Add Flutter to existing app
# Add Flutter to existing app
Add-to-app
It’s sometimes not practical to rewrite your entire application in Flutter all at once. For those situations, Flutter can be integrated into your existing application piecemeal, as a library or module. That module can then be imported into your Android or iOS (currently supported platforms) app to render a part of your app’s UI in Flutter. Or, just to run shared Dart logic.
In a few steps, you can bring the productivity and the expressiveness of Flutter into your own app.
The add-to-app
feature supports integrating multiple instances of any screen size. This can help scenarios such as a hybrid navigation stack with mixed native and Flutter screens, or a page with multiple partial-screen Flutter views.
Having multiple Flutter instances allows each instance to maintain independent application and UI state while using minimal memory resources. See more in the multiple Flutters page.
Let’s assume that you have an existing app at some/path/EVChargingStationAndroid
, and that you want your Flutter project as a sibling:
cd some/path/
$ flutter create -t module --org com.github.alanrb ev_charging_station_fluttersome/path/
├── ev_charging_station_flutter/
│ └── build/host/outputs/repo
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
└── EVChargingStationAndroid/
│ └── settings.gradle
└── EVChargingStationiOS/
│ └── Podfile
│ └── Flutter
│ └── Debug
│ └── Release
Android
Setup — Depend on the Android Archive (AAR)
Let’s assume you built a Flutter module at some/path/ev_charging_station_flutter
, and then run:
cd some/path/ev_charging_station_flutter
flutter build aar
You will get the output:
💪 Building with sound null safety 💪Running Gradle task 'assembleAarDebug'... 43.4s
✓ Built build/host/outputs/repo.
Running Gradle task 'assembleAarProfile'... 40.8s
✓ Built build/host/outputs/repo.
Running Gradle task 'assembleAarRelease'... 41.3s
✓ Built build/host/outputs/repo.Consuming the Module
1. Open <host>/app/build.gradle
2. Ensure you have the repositories configured, otherwise add them: String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url '/some/path/EV-Charging-Station/ev_charging_station_flutter/build/host/outputs/repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
} 3. Make the host app depend on the Flutter module: dependencies {
debugImplementation 'com.github.alanrb.ev_charging_station_flutter:flutter_debug:1.0'
profileImplementation 'com.github.alanrb.ev_charging_station_flutter:flutter_profile:1.0'
releaseImplementation 'com.github.alanrb.ev_charging_station_flutter:flutter_release:1.0'
}
4. Add the `profile` build type: android {
buildTypes {
profile {
initWith debug
}
}
}To learn more, visit https://flutter.dev/go/build-aar
Let’s open the settings.gradle and edit the section under dependencyResolutionManagement
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
google()
mavenCentral()
maven {
url '../ev_charging_station_flutter/build/host/outputs/repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
}}
rootProject.name = "EVChargingStation"
include ':app'
Next step, open app/build.gradle in host app EVChargingStationAndroid
so that it includes the dependency:
debugImplementation 'com.github.alanrb.ev_charging_station_flutter:flutter_debug:1.0'
profileImplementation 'com.github.alanrb.ev_charging_station_flutter:flutter_profile:1.0'
releaseImplementation 'com.github.alanrb.ev_charging_station_flutter:flutter_release:1.0'
Implement FlutterFragmentActivity from your app
- In Application:
class App: Application() {
lateinit var engines: FlutterEngineGroup
override fun onCreate() {
super.onCreate()
engines = FlutterEngineGroup(this)
}
}
- Create EngineBindings to binds a FlutterEngine instance and a channel for communicating with that engine.
class MainActivity : FlutterFragmentActivity() {
private val engineBindings: EngineBindings by lazy {
EngineBindings(activity = this, entrypoint = "main", channelName = "multiple-flutters")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
engineBindings.attach { call, result ->
when (call.method) {
"station_show_details" -> {
// Handle event
result.success(null)
}
else -> {
result.notImplemented()
}
}
}
}
override fun onDestroy() {
super.onDestroy()
engineBindings.detach()
}
override fun provideFlutterEngine(context: Context): FlutterEngine {
return engineBindings.engine
}
}
iOS
Setup — Embed application and plugin frameworks in Xcode and Flutter framework with CocoaPods
Setting up CocoaPods
- In case your host project doesn’t already use CocoaPods, you need to create a
Podfile
. If you already have a Podfile created in your iOS project, then you can skip the first step. - Run this command from the iOS project directory:
cd some/path/EVChargingStationiOS
pod init
pod install
- Alternatively, instead of distributing the large Flutter.framework to other developers, machines, or continuous integration systems, you can instead generate Flutter as CocoaPods podspec. This produces a
Flutter.podspec
instead of an engine Flutter.framework. The App.framework and plugin frameworks will be generated as described in Option B.
flutter build ios-framework --cocoapods --output=../EVChargingStationiOS/Flutter
Output:
Building frameworks for com.github.alanrb.evChargingStationFlutterSDK in debug mode...
├─Creating Flutter.podspec... 2ms
├─Building App.xcframework... 33.6s
Running pod install... 1,546ms
├─Building plugins... 12.7s
└─Moving to ../EVChargingStationiOS/Flutter/Debug 132ms
Building frameworks for com.github.alanrb.evChargingStationFlutterSDK in release mode...
├─Creating Flutter.podspec... 1ms
├─Building App.xcframework... 60.2s
├─Building plugins... 13.7s
└─Moving to ../EVChargingStationiOS/Flutter/Release 162ms
Frameworks written to /some/path/EV-Charging-Station/ev_charging_station_flutter/../EVChargingStationiOS/Flutter.
Host apps EVChargingStationiOS
using CocoaPods to add Flutter to Podfile:
pod 'Flutter', :podspec => 'Flutter/Debug/Flutter.podspec'
or
pod 'Flutter', :podspec => 'Flutter/Release/Flutter.podspec'
Run pod install
from some/path/EVChargingStationiOS
again and you should see Flutter framework pods being installed.
You can now open the EVChargingStationiOS
project in Xcode again by directing Xcode to the some/path/EVChargingStationiOS/EVChargingStationiOS.xcworkspace
file, (not the .xcodeproj
file!).
Your project layout in Xcode now looks as follows:
some/path/
├── ev_charging_station_flutter/
│ └── build/host/outputs/repo
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
└── EVChargingStationAndroid/
│ └── settings.gradle
└── EVChargingStationiOS/
│ └── Podfile
│ └── Pods/
│ └── Flutter/
│ └── Debug
│ └── Release
Add a build phase for building the Dart code
- For example, you can drag the frameworks from
some/path/EVChargingStationiOS/Flutter/Release/
in Finder into your target’s Build Settings > Build Phases > Link Binary With Libraries. - In the target’s build settings, add
$(PROJECT_DIR)/Flutter/Release/
to the Framework Search Paths (FRAMEWORK_SEARCH_PATHS
).
- By Build Settings > User-defined, add a
FLUTTER_ROOT
as the Flutter sdk path.
- And then select the Build Phases tab, add
Thin Binary
script as follows:
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed_and_thin
Implement FlutterViewController from your app
To launch a Flutter screen from an existing iOS, you start a FlutterEngine
and a FlutterViewController
.
The
FlutterEngine
serves as a host to the Dart VM and your Flutter runtime, and theFlutterViewController
attaches to aFlutterEngine
to pass UIKit input events into Flutter and to display frames rendered by theFlutterEngine
.
In AppDelegate:
import Flutter
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let engines = FlutterEngineGroup(name: "multiple-flutters-group", project: nil) func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}import Flutter
import FlutterPluginRegistrantclass MainVC: FlutterViewController {
private var channel: FlutterMethodChannel?
init(withEntrypoint entryPoint: String?) {
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
let newEngine = appDelegate.engines.makeEngine(withEntrypoint: entryPoint, libraryURI: nil)
GeneratedPluginRegistrant.register(with: newEngine)
super.init(engine: newEngine, nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
} override func viewDidLoad() {
super.viewDidLoad()
channel = FlutterMethodChannel(name: "multiple-flutters", binaryMessenger:self.engine!.binaryMessenger)
channel!.setMethodCallHandler { (call: FlutterMethodCall, result: **@escaping** FlutterResult) in
if call.method == "station_show_details" {
// Handle event
result(nil)
} else {
result(FlutterMethodNotImplemented)
}
}
}
}