Adding Flutter to your existing iOS and Android codebases

Paul Ruiz
12 min readJul 28, 2021

--

Whether you’re working for a small agency that creates mobile apps for an assortment of clients, or you’re developing for a massive corporation with hundreds of internal applications, supporting multiple codebases can be difficult and expensive (if it’s done at all). One scenario we have seen surprisingly often is that some companies have dozens, if not hundreds, of apps written for one major mobile platform, but nothing for the other. This leaves a lot of their users, often employees, unable to do the same tasks as efficiently as others, which can potentially lead to lost value.

One tool that can help solve these problems is Flutter, a UI framework that can support multiple platforms from a single codebase. While in a perfect world you would be able to create your apps from scratch using Flutter, that plan generally isn’t going to work out when a company has already put time and money into developing an app for one, if not more, platforms.

In this tutorial, we’re going to take a look at a more pragmatic approach to converting your existing iOS or Android apps to Flutter methodically over time by learning about a feature called add-to-app. While this feature doesn’t immediately provide you with a full-fledged Flutter app, it does allow you to maintain feature parity and stability with your current codebase during the process, rather than requiring a full refactor that could be filled with unexpected issues and ‘gotchas.’

As we move through this tutorial we start with a simple base-case app, similar to what you’d expect from a “Hello World” example, for both iOS and Android rather than adding the complexity of an already existing real-world application. Afterwards we will create the infrastructure necessary to add new views (written in Flutter) for each platform. When implementing add-to-app, the expectation is that you already have some experience with Flutter, but this tutorial attempts to keep things as simple as possible in order to focus on how to add Flutter to a non-Flutter app. In this way, you end up having the necessary vocabulary and know where to look when you are ready to try it yourself.

With that, let’s get started!

Flutter setup

If you haven’t already installed the Flutter SDK on your computer, now is a great time to do that. Go ahead and follow this link to find instructions for setting up your machine. I’ll wait here for you :) If you’d rather read ahead without following along, that’s also perfectly fine.

All set? Great!

So the first step in adding Flutter to an existing app is, unsurprisingly, creating a Flutter component to add to the app. From a command line interface, navigate to a directory where you would like to save your Flutter module and run the following command with the Flutter CLI tool:

flutter create --template module add_to_app_flutter_module

This creates and places a base Flutter application in a new directory named add_to_app_flutter_module, though you should feel free to name your module whatever you want — this tutorial assumes that you’re using the add_to_app_flutter_module name.

iOS setup

Typically, you already have an existing iOS or Android app in place when implementing the add-to-app feature. For this tutorial, you’ll create new apps from scratch in order to focus on the implementation fundamentals. You’ll start by creating a brand new iOS app from Xcode. If you’re not using a Mac computer or developing for iOS, feel free to jump ahead to the Android section or continue reading to learn about this process. I’ll only be a little disappointed that you didn’t read everything. Launch Xcode. When presented with the first options screen, select App and click Next.

On the next screen, fill in the text fields as appropriate. For this tutorial, use the Storyboard interface, Swift for the language, and UIKit App Delegate for the life cycle.

At this point, you should be prompted to create a new directory somewhere on your computer and able to place your app into it. For this tutorial, save the new iOS project folder within the same parent directory as the Flutter module you created earlier. Once that directory is created, you end up in the Xcode project screen with a folder structure similar to this one:

Returning to the command line, navigate into the new iOS project directory that you created in the last step and initialize CocoaPods using the following command:

pod init

After the Podfile is initialized, open it from the CLI and replace its contents with the following (remember to change the target name from Add-to-App to reflect your own app name, and the flutter_application_path to match your Flutter module’s path if you’re using different values):

Once the Podfile is updated, save the file and run the following command to link the Flutter module to the new iOS project:

pod install

Opening a default Flutter page from an iOS app

Now that the Flutter module and iOS project are linked, it’s time to learn how to navigate from an iOS ViewController to a Flutter page within the mobile app. Start by opening up the AppDelegate.swift file and setting the class to extend FlutterAppDelegate instead of the default UIAppDelegate. You also want to define a new FlutterEngine object, which is a container for the Flutter environment that is used to bridge the native iOS app and the Flutter class:

To finish the simpleFlutterAppDelegate class, create a new application function that registers the FlutterEngine when launching the iOS app:

That’s all you need to do in the AppDelegate class (for now!). To launch the default Flutter screen, go to the project’s ViewController.swift file (though you could use any ViewController in a more fleshed-out app). Add a new function named showFlutter() that retrieves the FlutterEngine and creates a new FlutterViewController object using the default Flutter entry point before displaying it:

Next, you need a way to call that function. For simplicity’s sake, define a button on the screen directly in the Swift code, though you can use any other navigation schema or technique that suits you. For this tutorial, create a new UIButton that’s centered in the middle of the screen, assign the new showFlutter() function to the button’s action, and then attach it to the view, all from the viewDidLoad() lifecycle function:

Try running the app now. If everything turned out as expected (fingers crossed!), then you should be able to launch your iOS app, click on the Show Flutter! button, and watch a new Flutter screen pop up:

Android setup

Now that you have the iOS/Flutter combination working, it’s time to try setting up an Android app. Just like you did with the iOS project, create a new Android project and select the Basic Activity template on the first screen.

On the next screen, fill in appropriate information for the name and package name. To keep everything similar to the iOS version of this sample, save your project under the same parent directory as the Flutter module and iOS app. You also want to make sure to set the project’s language to Kotlin for this walkthrough, though the same add-to-app logic holds for Android apps written in Java. When you’re ready, click the blue Finish button.

Now that you have a base Android project, add the Flutter module that you created earlier. You do this by going to File -> New -> New Module…

From there, go to the Import Flutter Module option at the bottom of the new window and add the Flutter module location before clicking on the blue Finish button.

Next, open the settings.gradle file and replace its content with the following:

The primary parts that matter here are the binding and adding the include_flutter.groovy file to the project. After that’s done, go to the project-level build.gradle file (located in the root level directory of your Android project) to add an allprojects block so you can compile the app (this may not be required later, but it’s an issue I ran into with Android Studio Arctic Fox, so I’m writing it here in case it helps someone :))

Finally, open the app-level build.gradle file (located under the /your_project_name/app directory) and add one line to the dependencies node to include the Flutter module as a source in the Android project:

At this point, the Android app should compile and build, plus you’ll see the Flutter module in the IDE

Opening a default Flutter page from an Android app

Now that the setup process for your Android app is done, you need to prepare to launch the app with the new Flutter component. Luckily, this is relatively easy now that the setup is done. Start by opening the AndroidManifest.xml file. Flutter add-to-app uses a custom FlutterActivity to display the Flutter content in Android, so you need to make sure to declareFlutterActivity in the manifest by adding the following block within the application tag:

Next, open the MainActivity.kt file and replace the Snackbar that gets shown by the app’s FloatingActionButton with the following code to launch the new FlutterActivity.

Now when you click on the FloatingActionButton you should see the Flutter page pop up directly in your app!

In addition to being able to launch a full Activity screen, similar to what you’ve already done in iOS, Android has the added benefit of being able to launch Flutter components as a part of a Fragment or a custom View. While those two techniques are out of the scope for this tutorial, you can find out how to use a FlutterFragment and FlutterView in the official documentation.

Opening additional Flutter screens

While it’s great that you can open a Flutter screen directly from a native iOS or Android app, it doesn’t really do as much as you may want, considering the whole idea behind using add-to-app was that you could slowly implement a variety of features in Flutter. To pull that off, you most likely need multiple entry points and various Flutter components. Fortunately there is a way to create multiple Flutter instances within a native app, though it’s worth noting that this functionality is experimental at the time of this writing. While there’s a good chance surface level things will stay the same, there’s also a chance that syntax or other details may change later.

Start by updating the code in the Flutter module to support a second screen by opening main.dart from of the flutter module/lib directory. In main.dart, declare your first new entry point by adding the following lines just under the declaration for main(). Note that this code snippet includes an annotation designating this method as a new entry point within your app.

MySecondAppScreen simply returns a new MaterialApp with a green theme and a new title so you can distinguish it from the main() entry point:

Next, you might notice that you need to create another code block for MySecondaryHomePage. This is a new StatefulWidget that contains a State object for the Flutter screen:

Finally, create the new State object. For this example, the widget displays an AppBar and a Text widget:

You now have two separate Flutter screens to launch from your native app. Next, you’ll implement the new screen in your existing Android demo app.

Multiple Flutter entry points in Android

This expanded add-to-app feature works by creating multiple instances of the FlutterEngine class (the same tool used for displaying the single default Flutter screen) and storing them in a FlutterEngineGroup, then calling the appropriate engine when necessary. First, create a new Application class that initializes the FlutterEngineGroup:

Next, create a helper class, in this case named EngineBindings, that takes the entry point’s name and lazily loads it into the FlutterEngineGroup so it can be displayed within your native application. This is loaded lazily because you want to make sure that the application has had a chance to fully load before it starts creating the FlutterEngines, otherwise you can run into an unexpected (and frustrating to debug) race condition.

The last class you need to add extends theFlutterActivity that you used in the previous Android section. Create a new Kotlin class file named SingleFlutterActivity.kt that extends FlutterActivity:

In this file, initialize the EngineBindings by passing in the new entry point name (secondary”, in this case) to match the name of the entry point you added in the Dart file, and write a helper method for retrieving the appropriate engine:

To complete theFlutterActivity, use the newly created engine to display the Flutter screen from onCreate().

Next, you just need to do a few more things to finish this demo application. Returning to MainActivity, go to the FloatingActionButton that was originally used to launch the main Flutter screen, and change the Intent so it launches the new SingleFlutterActivity class.

Finally, open AndroidManifest.xml and associate the new Application class with the application tag, and add an activity tag for SingleFlutterActivity.

You should now be able to run the app, click on the FloatingActionButton, and see the new screen rather than the default Flutter widget:

Multiple Flutter entry points in iOS

Are you still with me? Great!

Next, you’ll add support for multiple entry points in the iOS demo app. Going back into Xcode, open the AppDelegate class and replace all of the code with this simplified version that creates a single FlutterEngineGroup that can be accessed throughout the app:

Similar to what you did in the Android app, create a new file named SingleFlutterViewController.swift that extends the standard FlutterViewController. This class accepts a String with the name of the entry point you want to use, and then creates and displays a new FlutterEngine:

Finally, return to the base ViewController class and update the showFlutter() function so that it brings up the new SingleFlutterViewController class with a specified entry point:

After you’ve finished updating the code, update the Podfile to use the latest version of the Flutter module since you added the new entry point and screen code to main.dart. Once complete, you should be able to build and run your iOS app to see your native code switch to your new Flutter component.

Summary

Hey, you made it! Congratulations!

In this tutorial, you looked at how you can iteratively add Flutter to your existing Android and iOS apps in order to create a unified and more easily maintained code base. You’ve seen how you can add Flutter from a single entry point on both platforms, as well as how you can create multiple entry points. If you’re interested in learning more, I’ve included a link below to Flutter’s official documentation page that gives you more of the nitty-gritty details around add-to-app, as well as a link discussing Platform Channels, which allow you to communicate back and forth between your Flutter and native level code. Finally, check out the links that discuss plugins and how you can write your own to make developing for multiple platforms with Flutter even easier for you and the developer community.

We’re looking forward to seeing your cross-platform apps in action!

--

--

Paul Ruiz

Developer Programs Engineer on Android, Maker, @ptruiz_dev