skip to content

Add-to-App Embedding

Add-to-app hosts a Flutter module inside an existing Android or iOS app through FlutterActivity, FlutterFragment or FlutterViewController. Interviewers probe engine pre-warming and memory.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

4

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.
open as a page

In Flutter's add-to-app, what is a Flutter module, and how does an existing Android or iOS app show one Flutter screen from it?

level: juniorimportance: should knowfreq 32%

basics

~20 s

A Flutter module is a Flutter project built to be embedded in an existing native app. Android launches it through FlutterActivity (or FlutterFragment), iOS through a FlutterViewController; each runs the module's Dart entrypoint in a FlutterEngine.

open as a page

In Flutter add-to-app on Android, when do you embed a FlutterFragment instead of a FlutterActivity, and what must the host activity do for it?

level: middleimportance: should knowfreq 22%

basics

~20 s

Use FlutterFragment when Flutter is only part of a native screen, such as a card or a tab. The host activity must forward OS calls like onPostResume, onNewIntent, back presses, permission results, onUserLeaveHint and onTrimMemory to the fragment.

open as a page

In Flutter add-to-app, how does FlutterEngineGroup reduce the memory and startup cost of several Flutter screens, and what stays separate between its engines?

level: seniorimportance: nice to knowfreq 17%

basics

~20 s

Engines spawned from one FlutterEngineGroup share reusable resources — GPU context, font metrics and the isolate group snapshot — so each extra instance costs about 180 kB and starts faster. Each engine still runs its own isolate with separate Dart state.

open as a page