skip to content

In Flutter add-to-app, why pre-warm a FlutterEngine and store it in FlutterEngineCache, and what does that engine keep doing once pre-warmed?

level: seniorimportance: must knowfreq 36%

answer

  1. warm-up moves before the tap
  2. executeDartEntrypoint starts Dart immediately
  3. withCachedEngine uses the same ID
  4. engine outlives the activity
  5. initial route set before executing

basics

~20 s

Pre-warming creates a FlutterEngine and runs its Dart entrypoint ahead of time, so the Flutter screen appears almost immediately. The cached engine keeps running Dart and holding memory after its screen closes, until you destroy it.

solid answer

~40 s

Every `FlutterActivity` or `FlutterFragment` otherwise creates its own engine, with a non-trivial warm-up before the first frame. Pre-warming moves that cost earlier: create `FlutterEngine(context)`, call `dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())`, and `FlutterEngineCache.getInstance().put("loyalty_engine", engine)`; later, `FlutterActivity.withCachedEngine("loyalty_engine").build(context)` shows it with far less delay. The trade-offs: Dart starts executing the moment the entrypoint runs — `runApp` behaves as in a zero-size window until a UI attaches — and the cached engine **outlives** any activity or fragment, keeping its isolate, state and memory until you call `FlutterEngine.destroy()` (or build with `destroyEngineWithActivity(true)`). An initial route cannot be passed to `withCachedEngine`; set it with `navigationChannel.setInitialRoute` before executing the entrypoint. On iOS, the same pattern is `FlutterEngine(name:)`, `run()`, and `FlutterViewController(engine:nibName:bundle:)`.

code

kotlin · 3 lines
kotlin
// Tear down when the loyalty feature is no longer needed (e.g. user logs out).
FlutterEngineCache.getInstance().get("loyalty_engine")?.destroy()
FlutterEngineCache.getInstance().remove("loyalty_engine")

go deeper

for a junior

Know that pre-warming creates and runs an engine ahead of time and FlutterEngineCache hands it to FlutterActivity by ID.

for a middle

Explain executeDartEntrypoint, withCachedEngine, setting the initial route before execution, and the iOS equivalent with run() and initWithEngine.

for a senior

Decide when to pre-warm from real usage and release-build measurements, and manage engine lifetime with destroy() to avoid idle memory and stray side effects.

for a principal

Set an engine budget for the app: which Flutter features get a warm engine, when it starts and stops, and how memory is monitored on low-end devices.

## The cost you are moving Showing Flutter for the first time runs several steps: find and load the engine's libraries, start the Dart VM (once per process), create an isolate for this engine, run the Dart entrypoint, then attach a UI surface and render. With an implicit engine, all of this happens after the user taps — so a native airline app opening its Flutter loyalty screen shows a blank pause. **Pre-warming** does everything except attaching the UI ahead of time, at a moment you choose. ## The Android pattern ```kotlin class AirlineApp : Application() { lateinit var loyaltyEngine: FlutterEngine override fun onCreate() { super.onCreate() loyaltyEngine = FlutterEngine(this) loyaltyEngine.navigationChannel.setInitialRoute("/loyalty") loyaltyEngine.dartExecutor.executeDartEntrypoint( DartExecutor.DartEntrypoint.createDefault() ) FlutterEngineCache.getInstance().put("loyalty_engine", loyaltyEngine) } } ``` Then, from any native screen: ```kotlin startActivity( FlutterActivity.withCachedEngine("loyalty_engine").build(this) ) ``` - `FlutterEngineCache` is a process-wide map from an ID you choose to an engine. Use the same ID in `withCachedEngine` for `FlutterActivity` or `FlutterFragment`. - The initial route must be set **before** `executeDartEntrypoint`; `withCachedEngine` has no initial route, because the Dart code is already running. ## The iOS pattern ```swift let loyaltyEngine = FlutterEngine(name: "loyalty engine") loyaltyEngine.run() GeneratedPluginRegistrant.register(with: loyaltyEngine) // later let vc = FlutterViewController(engine: loyaltyEngine, nibName: nil, bundle: nil) present(vc, animated: true) ``` ## What the pre-warmed engine keeps doing | Aspect | Behaviour | |---|---| | Dart execution | Starts the moment the entrypoint runs; `runApp` builds as if in a zero-size window until a UI attaches | | Lifetime | Outlives every `FlutterActivity`, `FlutterFragment` or view controller that displays it | | State | Kept between visits: navigation stack, providers, caches | | Memory | Held while idle: isolate heap, engine resources, loaded images | | Stopping | Only when you call `FlutterEngine.destroy()` (or opt in with `destroyEngineWithActivity(true)`) | While no UI is attached, the Flutter app sees `AppLifecycleState.detached`, so code that must not run before display (analytics, network polling) should check the lifecycle or wait for a signal from the host. ## When to pre-warm, and when not 1. **Pre-warm** when the Flutter screen is frequent or latency-sensitive, and there is a good moment to start it (app start, or when the user opens a menu that leads there). 2. **Do not pre-warm** when the screen is rare, when there is no good heuristic for when to start the VM, or when state need not survive between visits — an implicit engine then costs nothing until used. 3. **Measure in release builds**; debug builds have very different start-up characteristics. A pre-warmed engine can also run non-UI Dart work — networking or data caching — independently of any screen, subject to the platform's background-execution rules. ## Mistakes interviewers look for - Pre-warming in `Application.onCreate` for a screen few users open, adding memory and start-up work for everyone. - Expecting `withCachedEngine(...).initialRoute(...)` to change the route of an already running engine. - Forgetting that the engine keeps running after the activity finishes, then seeing duplicate side effects or stale state. - Creating a new engine per visit and caching each, leaking one isolate per visit.

  • How do you give a cached engine a starting route other than '/'?
    Call `navigationChannel.setInitialRoute("/loyalty")` on the engine before `executeDartEntrypoint`. The cached-engine builders offer no initial route because the Dart code, and its first route, are already running by the time a UI attaches.
  • What does the Flutter app see while the pre-warmed engine has no UI attached?
    It runs normally but with no rendering surface — `runApp` behaves as if in a zero-size window — and the lifecycle state is `AppLifecycleState.detached`. Code that should wait for the screen to be visible must check for that.
  • When is an implicit, per-visit engine the better choice?
    When the Flutter screen is rarely shown, there is no good heuristic for when to start the Dart VM, and no state needs to persist between visits. Then pre-warming would only spend memory and start-up time for users who never open it.

saying these in an interview costs you the question

  • A cached FlutterEngine is destroyed automatically when its FlutterActivity finishes.
  • Pre-warming creates the engine but does not run any Dart code yet.
  • withCachedEngine can set a new initial route on the running engine.
  • Pre-warming every Flutter screen at app start is always best practice.
  • Pre-warmed engines use no memory until they are displayed.