Leaving Native behind: Our transition to an integrated Flutter experience

At funda, we recently shifted from native to cross-platform development, restructuring our teams in the process. Software engineer Filip Davidse explains how we rebuilt our mobile apps using the Flutter framework. A transition that had its challenges but was ultimately a rewarding journey. 

As a Flutter developer, I previously wrote a blog post about integrating Flutter during the intermediary phase of this project. In this post, I’d like to share the full story of how the entire project unfolded. 

Starting point 

For quite some time the native iOS and Android funda app was not only built natively, but done by a dedicated Apps team, consisting of iOS and Android engineers, a Product Owner and a UX Designer.  

Although this worked well, and the app was very popular and highly performant, there were some drawbacks. For example, it was hard to keep in sync with new features that were implemented by other teams on the funda website. Because the website and the app should offer the same features, it is important that these features are available on both platforms. And since both the iOS and Android apps’ codebases were organized in their own Mono repo it would have been hard to restructure them in such a way that they could be worked on independently by people from different teams. In other words, modularization would have been a challenge. So, we decided to get rid of the native apps and redo them using the popular cross-platform framework Flutter.

We chose Flutter because, at the time, it appeared to be the most promising cross-platform framework available. It was backed by frequent updates and improvements, had a very active and enthusiastic developer community, and offered the opportunity to use a modern programming language: Dart. 

The appeal of a cross-platform framework, of course, is that you don’t need to write the same code twice—once for your iOS app and once for your Android app—thus minimizing the risk of inconsistencies between the two. This also significantly reduces the effort required for code review and testing. 

What was next?  

The next phase of this project was to start working on Flutter and change the team structure, which meant that:  

  • The Apps team was dissolved, and the mobile engineers joined already existing Feature teams which, until that point, didn’t have mobile development capabilities. 
  • We started recreating existing features in Flutter 
  • New features were only created in Flutter 
  • We figured out how we could integrate this Flutter code into the existing native apps. 

We of course had to make sure the existing native funda app was still performing well, as there were more than a million users of the app. But we basically stopped writing native code, other than what was necessary for integrating our new Flutter pieces into the native app.  

Since I’ve already written a detailed blog post about this integration process, I won’t go into it further here. For more details, check out my previous blog post mentioned earlier.

However, I’ll briefly highlight the most important points below: 

  • We tried to turn every new feature, or separate existing feature, into a new Flutter package, and then integrated all those packages into the native apps. This is where cross-platform development really shines. If we have a Flutter package for a certain feature, we can use that package for both iOS and Android platforms. That means you write the code once and use it in two places.  
  • We had to use a trick to integrate those packages into the existing native apps though. If you want to integrate Flutter into a native app, you can only integrate 1 Flutter module. A Flutter module is like a package but specific for integration into native apps. We then had that one module depending on all these different Flutter packages. This allowed the native apps to reach those packages (which contained the features) through the module.  

Vertical team structure 

As mentioned, when we decided to restructure the team, we embraced a vertical approach. Therefore, our teams have been organized around specific features, rather than technical layers. Each Feature team is cross-functional, consisting of back-end developers, front-end developers, Flutter developers, a designer, a QA engineer, a product owner and a data analyst. This structure allows for better collaboration and faster delivery of features, as each team has end-to-end responsibility for their respective features. 

For instance, in the autumn of 2023 we updated a feature that allows users to save a search. This means that when a user searches for a house, specifying for example a price range and a city, this setting can be saved. Depending on their preferences, the user will receive emails and/or push notifications keeping them up to date about houses becoming available on the market that match their saved search. We made it possible to save up to 5 searches.   

A dedicated Feature team worked on this, integrating it seamlessly into both the website and the Flutter app. In the past, when the Apps team was still in place, there might have been differences in planning, alignment or details of the implementation of such a feature on the web and in the apps. Having all the relevant people in a Feature team eliminated such challenges.  

Fully Flutter: Our complete app migration 

The final phase of this project was reached when all parts of the native apps were recreated in Flutter, allowing us to remove the native apps and replace them with a Flutter-based app.

This phase of the project was reached in the summer of 2023, when we gradually started rolling out the new app – carefully monitoring its stability and the errors that we could track, of course. For error tracking we used Datadog, a very elaborate tool that allows you to get many insights into your app's performance, and even how users interact with it. This is particularly useful if you want to know how to recreate a certain flow which leads to an error or a crash.  

There is, however, one key ingredient missing in my explanation so far. If every Feature team creates a Flutter package which encapsulates a specific feature, how do these end up in an app which is fully developed in Flutter? This is where the Platform Apps (PLAPPS) team comes in.  

This team plays a crucial role in our development process. It is responsible for integrating the feature-specific Flutter packages created by the Feature teams into our main application. Additionally, the Platform Apps team handles essential functionalities such as routing, navigation, authentication and push notifications.  

In addition to their integration responsibilities, the Platform Apps team develops and maintains core Flutter packages that can be used across different Feature teams. These core packages provide common functionalities and ensure consistency throughout our app, reducing redundancy and promoting best practices. 

The Platform Apps team also take care of the actual release process on the App Store and Google Play Store, which includes developing and maintaining the automated release processes. 

Tooling 

One of the significant changes we've made over the past year is transitioning from Unpub to Cloudsmith for package management. Cloudsmith provides us with a more robust and reliable platform for managing our Flutter packages, enhancing our overall development workflow. Unpub is a self-hosted private Dart Pub server, which requires some effort to keep running reliably.

The private Dart Pub server, on the other hand, is hosted by Cloudsmith and is straightforward to integrate into a workflow. Another practical feature of Cloudsmith is that it allows you to publish development packages, which can be used to test features, without running the risk of ending up in your production Flutter app. 

With so many moving parts, you might wonder if it's still easy to release an iOS or Android app. The answer is yes, thanks to our efficient CI/CD pipelines. Our setup ensures that all the feature packages and the main app are tested and built seamlessly, reducing the risk of integration issues and ensuring smooth releases. 

See also: How to accelerate CI/CD integration with Backstage software templates 

So, what does our setup look like? We use Azure DevOps for our CI/CD infrastructure. Basically every package that we create, regardless of its purpose, has its own pipeline associated with it. It will run the tests, analyze your code and give you insights into how the package is doing. The pipeline runs when you create a pull request (pr), and on all subsequent commits. It also runs when a pr is merged, at which point it automatically publishes a new version of your package to Cloudsmith.  

The full Flutter funda app, into which we integrate all these packages, also has a pipeline, albeit a slightly more complicated one. It is divided into 5 steps, with the most important points as follows: 

  1. Build and test the funda Flutter app 
  • Install necessary dependencies like Flutter 
  • Pull in the latest versions of all the packages that the app depends on 
  • Run the analyzer and linter 
  • Run unit tests 
  • Publish a report of the tests 
  1. Build the iOS app 
  • Install iOS-specific things like certificates and provisioning profiles 
  • Update external dependencies 
  • Determine the version of the app itself 
  • Build the iOS app and generate an IPA file 
  1. Create internal release 
  • Use the build of the last step and upload to Firebase 
  • Test users can then use the Firebase Distribution service to test the build on their phone. 
  1. Publish internal release to BrowserStack 
  • Publish the previous build on BrowserStack 
  • Test users can now use the BrowserStack platform to test this build on multiple devices. 
  1. Optionally publish to TestFlight 
  • If desired, also publish the build on TestFlight to test it for the App Store.  
Example of debugging filter functionality in the funda app with Flutter DevTools to fine-tune UI elements.

The steps to handle the Android app are completely analogous to those for the iOS app. Having all these steps automated gives us enormous freedom to easily create builds for testing and for submission on the App Store and Google Play Store.  

Looking Ahead 

Our journey with Flutter continues to be exciting and rewarding. The vertical team structure, combined with the modular approach to Flutter packages and the support of the Platform Apps team, has significantly improved our development process.  

We have certainly faced challenges in transitioning from native to Flutter. For example: 

  • The hybrid phase: Integrating parts of Flutter into our existing native apps wasn’t easy. During this phase, you often encounter the limitations of the framework. Flutter truly shines when used for an entirely Flutter-based app. 
  • Performance issues: Since Flutter has only been around for about five years, it’s expected that bugs or issues occasionally arise within the framework itself. Fortunately, we’ve witnessed significant improvements over the years, which have made these issues less frequent and easier to manage. 
  • Finding skilled Flutter developers: It can be difficult to find developers with the right expertise in Flutter. Since the framework is relatively new, not everyone has the experience or knowledge to work with it efficiently. 

But for us, the benefits of our new approach definitely outweigh the drawbacks: 

  • Modular structure: Each feature is owned by a dedicated Feature team and is packaged in its own Flutter module, making development more organized and scalable. 
  • Cross-platform efficiency: You write the code once, and it then runs seamlessly on both iOS and Android, reducing development time and effort. 
  • Developer-friendly: Flutter offers an excellent developer experience, with fast iteration cycles and a wide range of tools that simplify the development process. 

As we look ahead, we are excited about the future possibilities with Flutter and the continued innovation it brings to our development process. Stay tuned for more updates as we continue to push the boundaries of what we can achieve with Flutter at funda. 

See also: Game changer: Tom's career shift from musician to engineer at funda

Question?
Do you have a burning question for Filip after reading this blog? Feel free to reach out to him via email.