How to Successfully Migrate from Native Android/iOS Mobile Apps to Flutter

Motivation 

Many companies are opting to rewrite their native Android and iOS mobile apps in Flutter due to its numerous benefits. Some of these are:

  1. It helps to reduce costs since native application development is typically more expensive, as each platform requires its own team of developers.
  1. By having only one Flutter team, the same resources that were previously dedicated to replicating features in Android and iOS can now be utilised to work on new features, resulting in an expedited development process.
  1. Flutter can improve the development process velocity by up to two-fold, as new features only need to be implemented once for both platforms, eliminating the need for different mobile development teams.

According to a survey that the Flutter’s team carried out, developers are also happier using Flutter. The survey concluded that 92% agree that Flutter reduces the time to build and publish new applications; 90% agree that Flutter enables faster development on existing applications, and 84% of developers agree that Flutter makes their applications more beautiful. [1]

At some point in your career as an Android, iOS, or Flutter developer, you may find yourself undertaking the process of migrating from native mobile apps to Flutter. In order to tackle this task successfully, it’s essential to keep certain key aspects in mind. To help guide you, my team and I have compiled a list of insights that can serve as a useful reference for successfully migrating from native Android/iOS mobile apps to Flutter.

Understanding the path 

Strategies for accomplishing the migration

There are three ways in which you can accomplish the migration process. The first one is to add a new Flutter library or module in the native applications. Therefore, you can add new features in Flutter without stopping the current development and thereby embed them to existing native apps, as per Figure 1. This strategy is also known as add-to-app feature. [2] 

Figure 1. Add-to-app feature. Flutter is integrated to the native apps as a library or module.

The second way is to make a Flutter project and add both Android and iOS applications into it as the following Figure 2 shows. This way, you would have your native projects running together in a single Flutter project, and through communication channels you would enable your Flutter features as long as you migrate them by passing messages from native to Flutter, and vice versa. [3]

Figure 2. A new Flutter project is created and contains both Android and iOS native apps.

The third way, and the less laborious, but not practical, way to migrate native apps to Flutter is to simply create a new Flutter project and start re-writing the native application from scratch as Figure 3 shows. This way is preferred if you have the chance to stop developing the native part, or to assign a dedicated team for the Flutter app. By choosing this way, it will also be easier to migrate the app since you will not have the need to set up an inter-platform communication and routing architecture between native and Flutter projects.

Figure 3. A new Flutter project is created and every feature that native apps contain will be migrated from scratch.

Which strategy should you opt for in your project?

Opting for the migration strategy that best fits your requirements can be challenging, since the decision does not depend completely on technical aspects, but also time availability and resources such as workforce and budget to complete the project. The following table presents some considerations you might want to bear in mind before making your final decision:

ApproachAdvantagesDisadvantages
Add-to-app feature– Allows for gradual migration without disrupting ongoing development.
– Easy to integrate with existing codebase.
– Native and Flutter modules can work together seamlessly.
– May lead to increased complexity in the codebase and development process (i.e. communication channels between different platforms).
– Requires familiarity with native app development.
– Limited access to certain Flutter functionalities.
Combined Flutter project– Allows for gradual migration without disrupting ongoing development.
– Increased access to Flutter features.
– Developers have access to the full range of Flutter APIs and packages.
– May lead to increased complexity in the codebase and development process (i.e. communication channels between different platforms).
– Can be more difficult to set-up and integrate with existing codebase since it may require significant refactoring of existing code. (i.e. rewrite build configuration from build types to build flavours supported by Flutter).
Requires familiarity with native app development.
Complete rewrite in Flutter– Offers the most comprehensive access to Flutter features.
– Allows for maximum flexibility and customisation.
– Can result in a cleaner and more efficient codebase.
– Can be the most time-consuming and resource-intensive approach.
– Requires a complete halt to ongoing native apps development.
– May require significant retraining of developers.

Table 1. Comparison between three different approaches to migrate from native apps to Flutter.

Common challenges when interoperating both native and Flutter projects

As mentioned previously in Table 1, there is a need to pay attention to the navigation system. It would be important to bear in mind that you will need to support navigation from:

  • Native screens to Flutter screens.
  • Flutter screens to Native screens.
  • Flutter screens to Flutter screens.

As long as you still support navigation between Native screens to Native screens.

You also can find that there are modules you want to migrate, but nobody has a clear idea about what acceptance criteria you should have in order to know you have completed the migration process. It might be because of the different states a screen can have due to many variables. In these scenarios you will have to be cautious when migrating in order to not leave something behind. It would be better to translate native code to Dart instead of trying to improve the flow or optimise code that the developer still does not understand completely.   

Furthermore, you are likely to encounter issues when migrating native-specific features to Flutter. For instance, at the moment there are no Flutter packages that support creating passes and cards on Google Pay or Apple Wallet. For these cases, you will not be able to fully migrate your native apps to Flutter. Instead, you will have some particular features on native side.

During the Migration 

Below are five distinct stages that you are likely to go through during the process of migrating a native app to Flutter. Whilst your individual situation may require additional considerations, here are the most common situations you can expect:

  1. Understanding your team, the project, and the client
  • Understand the client, their business, and the mobile application’s workings:
    programming languages, different user flows, use cases.
  • Decide on the most suitable methodology for monitoring your work and reporting progress, such as agile approaches like Scrum or Kanban.
  • Clarify your scope and determine which features to address first. This will help you filter out what you actually need to know when reading documentation and other resources.
  • Read the documentation for both Android and iOS apps and ask for diagrams, use cases, clarifications, and permission for the platforms you will use later on such as:
    • Jira, Azure Boards: for tracking your work.
    • Confluence, Miro: for roadmaps and product documentation.
    • Figma, Sketch, Zeplin: for UI designs and mockups.
    • Git, Firebase, AWS and other tools related to the project.
  1. Setting up the development environment
  • Determine which methodology best fits to deliver value to the client periodically, and to manage your team internally: Scrum, Kanban, Scrumban, cascade, among others.
  • Understand which product stages are defined: development, staging, production.
  • Set up your environment by installing the required tools: programming languages and frameworks (Java, Flutter…), dependencies, IDE (Android Studio, Xcode, VS Code), virtual emulators. Also, some extra tools might be necessary to set-up such as certificates, antivirus and VPN software according to your company’s security policies.
  • Understand the Git branching strategy the client is using and the repositories you need to have access to.
  • Clone the Android and iOS projects and download the libraries needed to compile the applications.
  • Run the applications on your testing device (real smartphone or emulator).
  • Check the feature’s screens you need to migrate and set debug breakpoints to understand how it works behind the scenes.
  • Make it a habit to inspect the logs at each stage of your activity within the application. This will familiarise you with frequent errors or API calls that may prove useful during the debugging process.
  1. Migrating a Feature from Native to Flutter
  • Examine the architecture and required libraries for the application. Evaluate the directory structure, which may include the source code, architecture diagrams, widget/integration/unit tests, CI/CD packages, and other scripts, to understand how resources are distributed throughout the project.
  • Once you understand how the feature is built, create a spike document to record your findings. This document can contain:
    • Description of the feature.
    • Total screens to migrate.
    • User flow explained: through diagrams, gifs, videos.
    • Post-migration evaluation: acceptance criteria for QA.
    • Mobile-specific features: dialer app, push notifications, required runtime permissions.
    • Resources and requests: for instance, access to Firebase or to RevenueCat in order to register the Flutter app and get an app key.
  • Contrast this spike document with the client to ensure that your understanding is accurate.
  • Establish a clear migration plan. For instance, you can start with a small and testable feature and then:
    • Focus on migrating its UI components first. 
    • Later, verify their responsiveness across different devices and screen sizes. 
    • Implement widgets state management. 
    • Write unit tests to verify the possible states of the widgets. 
    • Connect the widgets to the data source. 
    • Finally, use integration testing to test the feature as a whole.
  • Double-check that everything is working as expected for both Android and iOS devices: different screen sizes, dark mode, and other possible configuration changes.
  • Publish your first Flutter migration MR and ask for feedback from your reviewers to develop further features the right way.
  • Double-check the constraints and rules set-up on the CI pipeline to merge your code to the main branch such as linters, code coverage, etc.
  • Set feature flags to allow A/B testing in the native apps as you migrate more features.
  • When necessary, you can exchange information between native apps and Flutter using MethodChannel and EventChannel.
  • Understand how to generate and distribute app builds to QA using the platform the client has been using.
  • Tweak the feature you released if necessary as per QA feedback.
  • Draft a document that outlines the code style, standards, and other resources that you intend to employ. Share this document with your team to ensure that everyone is aligned with the established vision and approach to work. Remember that the quality of a development team is determined by the excellence and standardisation of their code.
  • For optimal efficiency, assign submodules to individual developers to work on so that the team can get progress in different tasks simultaneously.
  • Gain a thorough understanding of the roles within your team. Evaluate whether it’s appropriate for you to take on extra responsibilities, such as creating tickets or updating native code. Perhaps those tasks would be better suited for other members of the team.
  1. Considering important aspects of the project
  • Keep in mind the importance of accessibility through the client’s eyes.
  • Localisation should be a priority if the app targets a global audience.
  • Migrate instrumentation and analytics functionality if required.
  • Optimise the app’s performance and source code throughout the migration process. For instance, when you find some patches that a developer applied to the native code to hot-fix at some point of its lifecycle, it’s better to understand the global view of this to better migrate the feature since that code will likely end up with fewer lines of code. This will also make it clearer for other developers.
  1. Ensuring collaboration between the team and the client
  • Familiarise yourself with the client’s team. Identify the key individuals who can provide insights and expertise on technical challenges, UI design, and the product definition itself.
  • Schedule regular meetings with the client to discuss progress and ensure alignment with their expectations.
  • Conduct User Acceptance Testings (UAT) to obtain approval from the client, product owner, and testing users.
  • Leverage your teammates’ strengths, particularly those who specialise in Android and iOS code. Seek their guidance on relevant aspects such as project navigation, set-up and troubleshooting.
  • Before approaching the client directly, filter your questions and insights with your team to avoid generating unnecessary noise or issues.

Conclusion

It is possible to rewrite a native mobile app in Flutter, but some native code may still be required for specific functionalities. Migrating to Flutter can be considered when there is similar logic for both Android and iOS versions of the app and at least one native developer on the team.

To mitigate issues during the migration process, it’s crucial to conduct proper QA testing and Alpha & Beta testing, but the real test is when the application is used by real users with different devices and use cases. Therefore, the migration result cannot be considered stable until it has been thoroughly tested by real users. 

Additionally, having people with experience in native development is key for every native-to-Flutter migration project, particularly if the app requires high native feature dependency, as some features may need to be developed natively.

However, overcoming technical challenges throughout a migration process are a vital, but insufficient, part to successfully complete a project like this. Client’s business and needs, as well as effective collaboration with their team, are equally important since it will lead to more productive working relationships. Without a doubt, balance between technical proficiency and client-focused collaboration is essential to achieve the best possible outcome for the project.

References:

[1]. Does Flutter boost developer productivity?

https://medium.com/flutter/does-flutter-boost-developer-productivity-475f713724b3

[2]. Add Flutter to existing app

https://docs.flutter.dev/development/add-to-app

[3]. Migrating existing native Android/iOS applications to Flutter

https://www.agilevision.io/blog/migrating-existing-native-android-ios-applications-to-flutter