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

  1. Create a Podfile
    pod init

  2. 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.

  3. Install the CocoaPods dependencies
    pod install

  4. 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.

  1. Add the following imports at the top of AppDelegate.swift

    import Flutter

    import FlutterPluginRegistrant

  2. Create a lazy variable to hold the long lived flutter engine

    lazy var flutterEngine = FlutterEngine(name: "my_flutter_engine")

  3. 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

  1. Add the following imports at the top of DogBreedsViewController.swift

    import Flutter

  2. 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)

    }

  3. 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.