Irbisa · Cheatsheet

Add-to-App & Native Integration

FlutterEngine, multiple engines, embedding Flutter in an existing iOS or Android app, Pigeon, plugin vs FFI, size and startup

Senior Developer · 16 questions

  1. 01

    What is add-to-app, and how is a Flutter module created with flutter create -t module different from a Flutter app?

    Add-to-app embeds Flutter screens into an existing Android or iOS app instead of rewriting it.

  2. 02

    What is the difference between a FlutterEngine and the FlutterActivity or FlutterViewController that shows it?

    The FlutterEngine runs your Dart code: the isolate, the channels, the plugins and the renderer.

  3. 03

    How do you add a Flutter module to an existing iOS app: SwiftPM, CocoaPods or prebuilt frameworks?

    Today the documented route is Swift Package Manager: flutter build swift-package --platform ios generates a local package that you link in Xcode.

    # in the Flutter module: generates build/ios/SwiftPackages/, add it to Xcode as a local packageflutter build swift-package --platform ios # build settings of the host targetFLUTTER_SWIFT_PACKAGE_OUTPUT = $(SRCROOT)/../my_flutter/build/ios/SwiftPackagesFLUTTER_APPLICATION_PATH = $(SRCROOT)/../my_flutter   # optional: Xcode rebuilds Dart…
  4. 04

    How do you add a Flutter module to an existing Android app (as a source dependency or as AARs), and what does each cost?

    Either include the module's Gradle project in the host build (include_flutter.groovy, then implementation(project(":flutter"))), or publish AARs with flutter build aar into a local Maven repository and depend on them like any other library.

    // settings.gradle.kts, option 1: source dependency (every host build compiles Dart)apply(from = File(settingsDir.parentFile, "my_flutter/.android/include_flutter.groovy"))// app/build.gradle.kts: implementation(project(":flutter")) // settings.gradle.kts, option 2: AARs from `flutter build aar`dependencyResolutionManagement {…
  5. 05

    When do you embed Flutter on Android as a FlutterActivity, a FlutterFragment or a bare FlutterView?

    FlutterActivity for a full-screen Flutter flow, because it needs the least code; FlutterFragment when Flutter is part of a native screen, such as one tab; a bare FlutterView only when neither fits, because then you wire the lifecycle yourself.

    class HostActivity : FragmentActivity() {    private var flutterFragment: FlutterFragment? = null     override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.host)…
  6. 06

    Who registers plugins when you create a FlutterEngine yourself, and which plugins break in add-to-app?

    On Android a new FlutterEngine registers every plugin automatically, via GeneratedPluginRegistrant; on iOS nothing is automatic, so you call GeneratedPluginRegistrant.register(with:) on each engine you run.

    import Flutterimport FlutterPluginRegistrant @mainfinal class AppDelegate: UIResponder, UIApplicationDelegate, @preconcurrency FlutterAppLifeCycleProvider {    private let lifecycle = FlutterPluginAppLifeCycleDelegate()…
  7. 07

    How does the native host tell the Flutter module which screen to open and with what data, on the first start and on later visits?

    Before the engine runs, set its initial route or pick a Dart entrypoint and pass it arguments.

    // lib/main.dart of the module: the host runs main unless it names another entrypointvoid main() => runApp(const ShopApp()); // kept by the AOT compiler even though no Dart code calls it@pragma('vm:entry-point')void settingsMain() => runApp(const SettingsApp());…
  8. 08

    Find the bug: after the cart moved into the app as a Flutter module, this code locks the session every time the user goes back to a native screen.

    class SessionGuard with WidgetsBindingObserver {  SessionGuard(this.auth) {    WidgetsBinding.instance.addObserver(this);  }   final AuthService auth;   @override  void didChangeAppLifecycleState(AppLifecycleState state) {    // the user left the app: ask for Face ID when they come back    if (state == AppLifecycleState.paused) auth.lock();  }}

    In add-to-app, AppLifecycleState describes the Flutter view, not the app.

  9. 09

    Find the bug: every time the user opens Profile there is a blank pause, and the Flutter screen has forgotten everything from the last visit.

    @MainActorfinal class ProfileCoordinator {    func showProfile(userId: String, from host: UIViewController) {        let engine = FlutterEngine(name: "profile")        engine.run(withEntrypoint: nil, initialRoute: "/profile/\(userId)")        GeneratedPluginRegistrant.register(with: engine)         let flutterVC = FlutterViewController(engine: engine, nibName: nil, bundle: nil)        flutterVC.modalPresentationStyle = .fullScreen        host.present(flutterVC, animated: true)    }}

    showProfile creates and boots a brand-new FlutterEngine on every tap, so each visit pays the full engine start before the first frame and begins with an empty Dart heap.

  10. 10

    How do you debug and hot reload the Flutter part when the app is launched from Xcode or Android Studio as a native app?

    Run the host app in debug from Xcode or Android Studio, then run flutter attach in the module directory: it finds the running engine's Dart VM service and gives you hot reload, hot restart, DevTools and Dart breakpoints.

  11. 11

    Your app shows Flutter in several places at once: a tab, a card, a full-screen flow. Why use FlutterEngineGroup, and what can those engines share?

    Engines spawned from one FlutterEngineGroup share the loaded AOT code, fonts and GPU context, so each extra engine costs far less memory and starts faster than an independent one.

    class App : Application() {    lateinit var engines: FlutterEngineGroup     override fun onCreate() {        super.onCreate()        engines = FlutterEngineGroup(this)…
  12. 12

    A user goes native → Flutter → native → Flutter. Who owns navigation and the Back gesture across that boundary?

    The host owns the stack between screens and Flutter owns the stack inside its own view.

  13. 13

    How do you design the API between a native host and a Flutter module so two teams can change it safely?

    Treat it as a versioned contract: define it once in a Pigeon file, generate Kotlin, Swift and Dart from it, keep it small and coarse-grained, and make the host the owner of shared state such as the session.

  14. 14

    The host already ships a C++ core and a Kotlin/Swift SDK. How should the Flutter module call each: Pigeon, dart:ffi, ffigen or jnigen?

    Call the C++ core through dart:ffi: it is the same code on both platforms and needs no serialisation.

    import 'dart:ffi';import 'dart:io'; // The host already ships the C++ core: Android packages libcore.so,// iOS links it statically into the app binary.final DynamicLibrary _core = Platform.isIOS…
  15. 15

    What does adding a Flutter module cost the host app in download size and start-up time, and how do you keep that cost down?

    Size: the engine plus your AOT-compiled Dart, assets and plugins, several megabytes per CPU architecture.

    class App : Application() {    override fun onCreate() {        super.onCreate()        // cheap: loads libflutter.so and extracts assets on a background thread        FlutterInjector.instance().flutterLoader().startInitialization(this)…
  16. 16

    You are asked to migrate a large native app to Flutter screen by screen. How would you plan the add-to-app rollout?

    Start with a self-contained flow that has few links into the rest of the app, put the build pipeline, a thin host API and an engine strategy in place first, keep the host as owner of auth, navigation and app-wide state, and grow Flutter's share until inverting the hosting is cheaper.