Integrating Flutter into an existing native app might sound like a complex decision, but in practice, it's a powerful way to modernize your UI, iterate faster, and bring consistency across platforms — all without rewriting your whole codebase.
In this post, we'll walk through how to use Flutter as a module inside native Android and iOS apps using Flutter Add-to-App. We'll also show how to use GoRouter for navigation and Pigeon to bridge native and Dart logic in a type-safe, scalable way.
Flutter is great, but not every team is ready for a full rewrite — and you shouldn’t have to be. With Add-to-App:
Start by creating a new Flutter module:
flutter create --template module flutter_module
This creates a structure optimized for embedding. Inside this module, we recommend:
include ':flutter'
setProjectDir(':flutter', file('../flutter_module'))
dependencies {
implementation project(':flutter')
}
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/qr")
.build(this)
)
For production, consider caching a single engine instance to improve launch time.
flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)
Then:
pod install
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
flutterVC.setInitialRoute("/qr")
present(flutterVC, animated: true)
Inside your Flutter module:
final router = GoRouter(
routes: [
GoRoute(
path: '/qr',
builder: (context, state) => const QrScannerScreen(),
),
],
);
Then plug it into your `MaterialApp.router`:
MaterialApp.router(
routerConfig: router,
)
This makes your screens deep-linkable and easy to coordinate from native.
Pigeon helps you generate type-safe messaging code between Dart and platform code.
@HostApi()
abstract class QrApi {
String sendResult(String scannedCode);
}
flutter pub run pigeon \
--input pigeons/api.dart \
--dart_out lib/pigeon/api.g.dart \
--kotlin_out android/app/src/main/kotlin/com/yourcompany/QrApi.kt
--swift_out ios/Runner/QrApi.swift
class QrApiImpl : QrApi {
override fun sendResult(scannedCode: String): String {
Log.d("QR", "Received: $scannedCode")
return "Success"
}
}
Then register it:
QrApi.setUp(flutterEngine.dartExecutor.binaryMessenger, QrApiImpl())
Add-to-App isn’t just for transitional phases. It’s a powerful long-term architecture for hybrid teams and projects that need the best of both worlds.
With tools like Pigeon and GoRouter, integrating Flutter doesn’t just feel native — it is native. And once you're up and running, your team can start shipping faster with a consistent experience across platforms.
Whether you’re shipping a single screen or planning a larger migration, Flutter Add-to-App is ready when you are.
Pro tip: Reuse a single Flutter engine when possible and preload it early to improve performance on first navigation.
Written by Juan Garcia da Rosa, Senior Engineer at Leenspace.