Adding Flutter to an Existing iOS Application: Part 1
What’s flutter module?
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 as a library or module. That module can then be imported into your iOS or Android (currently supported platforms) app to render a part of your app's UI in Flutter. Or, just to run shared Dart logic.
What we are going to do?
In Part 1, we will create a Flutter module and integrate it into an existing iOS application - Dog breeds app.
In Part 2, we will customise the Flutter module to display the dog breed detail screen.
You can find the starter and completed projects on GitHub:
There are a few ways to embed Flutter in your existing iOS application, we will be using CocoaPods dependency manager as this will help us using flutter’s hot reload for ease of development.
We can also export the module as iOS frameworks and manually embed the frameworks into existing application's build settings in Xcode. This can be useful for teams that don't want to require every developer to have the Flutter SDK and may be, don’t want to use cocoapods as dependancy manager. We will also explore this option in another article.
Step-by-Step Integration
Prerequisites
Before we start, ensure you have the following installed on your machine:
Flutter SDK
CocoaPods
Step 1: Create a Flutter Module
First, you need to create a Flutter module that will be integrated into your app.
Download the starter project from part 1. Open terminal and navigate to DogBreeds
directory from starter project in the terminal. Run the following command to create a Flutter module:flutter create --template module my_flutter_module
Additionally, you can add this directory as submodule to your existing code if you want to track the flutter module changes in separate git repository.
Step 2: Integrate Flutter Module into iOS Project
Create a Podfile
pod init
Open the Podfile and replace with following
flutter_application_path = 'my_flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'DogBreeds' do
use_frameworks!
install_all_flutter_pods(flutter_application_path)
end
post_install do |installer|
flutter_post_install(installer) if defined?(flutter_post_install)
end
Note - You may receive warning about cocoapods overriding some build setting in your project. This may lead to issues in pods installation / configuration and hence it is recommended to use
$(inherited)
flag to your custom Xcode’s build settings before installing the pods.Install the CocoaPods dependencies
pod install
Open the newly created
DogBreeds.xcworkspace
file in Xcode and run the project once.
Step 3: Start a FlutterEngine
The FlutterEngine serves as a host to the Dart VM and your Flutter runtime, and the FlutterViewController attaches to a FlutterEngine to pass input events into Flutter and to display frames rendered by the FlutterEngine.
Also, showing a Flutter UI has a non-trivial latency cost and hence, You may see a white / black screen when you display your flutter UI for the 1st time. It's generally recommended to start a long-lived FlutterEngine well in advance for your application instead where you actually need one.
You can create the flutter engine anywhere in your application. For the DogBreed app, we will use AppDelegate
to lazily initiate a long lived flutter engine and use it to launch flutter UI.
Add the following imports at the top of
AppDelegate.swift
import Flutter
import FlutterPluginRegistrant
Create a lazy variable to hold the long lived flutter engine
lazy var flutterEngine = FlutterEngine(name: "my_flutter_engine")
Start the flutter engine and register plugins (not in scope of this article)
self.flutterEngine.run();
// Connects plugins with iOS platform code to this app.
GeneratedPluginRegistrant.register(with: self.flutterEngine);
Step 4: Show a FlutterViewController with your FlutterEngine
Add the following imports at the top of
DogBreedsViewController.swift
import Flutter
Add following function to
DogBreedsViewController.swift
func showFlutterScreen() {
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController = FlutterViewController(engine: flutterEngine,
nibName: nil,
bundle: nil)
self.navigationController?.show(flutterViewController,
sender: nil)
}
Call above function from table view delegate as following
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
self.showFlutterScreen()
}
Now, you have a Flutter screen embedded in DogBreeds iOS app.
Step 5: Run Your Application
Now that everything is set up, you can build and run DogBreeds application. When you click on any dog breed, you should see the Flutter content integrated seamlessly into DogBreeds iOS app.
Step 6: Debug Your Application
To debug your application, navigate to my_flutter_engine
directory and run flutter attach
.
If you have multiple targets available, select a specific target device.
The output may be similar to below,
Waiting for a connection from Flutter on iPhone 14 Pro...
Syncing files to device iPhone 14 Pro... 11.6s
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
That's it! You've successfully created a Flutter module and integrated it into your existing iOS application using CocoaPods. In the next part of this series, we'll explore using Flutter's MethodChannel and Pigeon to make API calls from the host app and display the dog breed detail screen from starter project
For the complete code and more details, refer to:
Part 1 - Focused on creating a Flutter module and integrating it into an iOS application.
Part 2 - Focused on communication between the Flutter module and the host application using Flutter's MethodChannel via Pigeon.