Starting a Flutter app from scratch is one thing, integrating it with an existing native app – one over 10 years old, in our case – is a whole different ballgame. It's a game you can play with two different tactics, explains funda’s Flutter Developer Filip Davidse.
No, this is not going to be another blog about how Flutter can help you write code once and run it on Android and iOS simultaneously, or even on web or desktop machines. So there are no tips or carefully composed lists of pros and cons about Flutter or for the similar solution known as Dart. If you want to know how to start your first Flutter project, I recommend you check out the excellent documentation on Flutter.
Our learnings at funda
Here I would like to discuss a real-world example of what it means for a company to incorporate Flutter into their existing native apps, with a special focus on release management. It is something that we have spent considerable amounts of energy on. But now we have a setup which works quite smoothly, and I wanted to share what we have learned with you.
Let us suppose that your company has one or more native apps (iOS and Android) available for users, like we have at funda. If at some point you decide that, going forward, Flutter is a very promising cross-platform technology and you really want to use it, what can you do?
Two options for using flutter
There are 2 options. One is to integrate bits of Flutter code into your existing native apps. The other is to start fresh with a Flutter app and rebuild the native apps from scratch. At funda we do both, but with a twist. Let me start by explaining the first approach.
Option 1: integrating Flutter into an existing app
Funda's iOS and Android apps are used by over one million people, meaning that every change we make to them has to be carefully considered. In the spring of 2021, we decided to start developing new features for the apps by using Flutter. At the same time, we changed the structure of the different development teams a bit.
Before we had all funda's mobile developers concentrated in the ‘Team Apps’. But when we started to use Flutter, we moved the mobile development process to the already existing feature teams. This meant that there would no longer be Team Apps, but the mobile developers would join the different feature teams, and those developers would start to use Flutter.
Let me give you a concrete example. When people use the funda website or app they do so to be able to search for houses that are for sale or rent. A user will typically enter some search criteria, like the name of a city or region, and a maximum price. This search will return in a list of houses matching those criteria, and by selecting a house the user can view the details of that house. Last year a new feature was created to add more information to that detail page about the neighbourhood, such as how many people live there, and what the average selling price of a house is.
Also read the blog: How we validated Flutter at scale for funda
This is a useful, clearly bounded feature, ideally suited for a feature team to work on. The multidisciplinary team develops the feature, the front-enders integrate it into the funda website, and the Flutter developers have to integrate it into the existing iOS and Android apps.
This is where it becomes a bit complicated. There is documentation on how to add Flutter code to your native app which you can view here. But this also states that packing multiple Flutter libraries (also known as modules) into an application is not supported.
Choosing a setup for your Flutter project
When you start a new Flutter project, you can choose between a Flutter app, a package, or a module. A Flutter app is what most developers would choose (and to be honest, this is the nicest to work with). A package is a library that can be integrated into a Flutter app or module. And a Flutter module is specifically for integration into an existing native Android or iOS app. So, the Flutter developer in a feature team would have to work on a Flutter module.
But what if there is more than one feature team? If there is more than one Flutter module, how can they be integrated in a native app?
The workaround is to create an extra Flutter module, which at funda we call an umbrella module, because it sits on top of – covers – the other modules, and imports them into one module.
This umbrella module is added as a dependency to the native project. For iOS it means a change to your Podfile like:
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
In the Podfile you can only do this once, which is the limitation I mentioned earlier. It is not possible to do:
flutter_application_path1 = '../../flutterModule1'
load File.join(flutter_application_path1, '.ios', 'Flutter', 'podhelper.rb')
flutter_application_path2 = '../../ flutterModule2'
load File.join(flutter_application_path1, '.ios', 'Flutter', 'podhelper.rb')
The key point here is to figure out where on your machine (or your pipeline) you have placed your umbrella module with respect to your Podfile. In this example the umbrella is located in a directory called flutterModules. In order to build your project, first run ‘flutter pub get’ in the directory of the umbrella project and subsequently ‘pod install’ in your iOS project. Then you can build and run the project.
In your native code you want to show, in some places, a view which comes from your Flutter modules. This can most easily be done using Dart entry points, as explained here.
To set up the entry points in the umbrella, define the entry points in your main.dart file as follows:
void myFeature() => runApp(const myFeatureApp());
Where myFeatureApp is defined within the Flutter module for that feature. The only thing that is left to do is to create the dependencies in the umbrella; in other words, add the other Flutter modules in your pubspec.yml file. This can be done in various ways. Let us suppose you have the myFeatureApp Flutter module. Then you could check out that repository on your machine (or in your pipeline) and import the module as follows:
Here you have to replace myfeaturepath with the actual path to the folder where my_feature is located in relation to your umbrella project.
This will work fine. And in fact we have used that approach for some time at funda. But there are some drawbacks. It is hard to do versioning on your different dependencies, and you must be aware of what branch you are on for each of the different repositories.
Privately hosting Dart packages
So, we have now taken another approach. If in general a Flutter developer is looking for some useful libraries, they head over to pub.dev and find what they need, for example an easy-to-use networking library. They can then import this easily into their own project and use it.
That is how we would like to use our own Flutter modules as well, but we do not want to publish them on Pub Dev, because then everyone would be able to use them.
Luckily you can run your own version, but privately. We do this by using unpub, a self-hosted private Dart Pub server.
This allows us to publish each of our Flutter modules to unpub. And then, since they are available as packages on our unpub server, we can import them from there into our umbrella’s pubspec, for example:
Releasing the app
You might wonder if, with so many moving parts, it is still easy to release an iOS or Android app. Luckily, it is relatively straightforward. In the diagram below, you can see our CI/CD setup for the iOS and Android apps.
So, what is going on here? In the top left there are the different Flutter modules being developed by different feature teams. It does not matter where the repositories are. The feature teams themselves are responsible for publishing their modules as a package to unpub. This can be a manual or automated process. On the right you can see the repositories for the native apps and the Flutter modules. In our case we use Github for them.
For our CI/CD processes we use Microsoft Azure, but the process would be similar if we used something else. In the diagram above the steps needed to release the iOS or Android app are outlined.
Clone only the umbrella repository and the native app repository.
Install other tools/dependencies, such as Flutter, CocoaPods, etc.
The umbrella is key here. Every other module is imported by the umbrella as a package. These packages are hosted on unpub, and reachable within Azure. Consequently, when in this step the command ‘flutter pub get’ is executed for the umbrella module, all external packages (from pub.dev) and internal packages (from our unpub server) are fetched.
This produces a framework that can be imported into the native (iOS/Android) app.
Build the native app, using output from Step 3, and all other necessary external (but native) dependencies.
Deploy the app to App Store Connect or Google Play Store.
And that is basically it. If all goes well, you end up with a native app that a user can install and interact with. Hopefully, without them realising that some parts were actually created using Flutter.
This is how we currently build and release our native apps at funda. But, as I already hinted at, we are also coming up with another strategy.
Option 2: starting with a new Flutter app
As I mentioned above, instead of integrating Flutter modules into an existing native app, you could also start from scratch with a completely new Flutter app. However, it would probably take a long time to completely rebuild the app. That is why at funda we have a separate team working on this approach. And they will not have to rebuild everything, because the Flutter modules that are produced by the different feature teams can actually be used in a Flutter app as well.
So, it is more a matter of creating a Flutter app that is able to seamlessly integrate all those existing modules, and handle more common things, like navigation and authentication.
It takes a lot of figuring out how to best integrate multiple bits of Flutter into your native app. It is certainly less straightforward than creating a full Flutter app. But once you have gone through that process, it is very feasible to have Flutter developers in different feature teams working on their Flutter modules, integrating those into a native app and releasing the new features. But you will still need iOS/Android expertise when it comes to integrating the Flutter code into the native apps.
Also read this from funda: How Golden Paths give our developers more time to actually develop