14 July 2020
How to add a Flutter screen to an existing Android app? (1/3)
From time to time, in some long term projects, there may appear an idea or a need to change the core technologies of mobile applications. It may be caused by a technological debt of an old project which was not maintained properly or there may be a need to redesign the whole application. In the second case, if you consider rewriting an application using a cross-platform technology like Flutter or React Native – it will be a long process. But have you ever wondered how you can add a Flutter screen to an existing Android and iOS app? Below, you will find a Flutter tutorial in which I show how to do that on Android.
My idea is to share my thoughts regarding this broad topic with you. That’s why I decided to write a few parts of this article and this is the first of the series. In this part we will start with:
- creating a new Flutter app module where we place our new screen,
- embedding that screen into an existing native Android app,
- reusing basic navigation from the old screen in the new one.
So, coming back to the “long process” mentioned in the introduction. Any use of cross-platform technology will require you to write the whole domain and data logic all over again. Only then you will see some ready designs for all new screens. What if you don’t need to rewrite the whole app right away and instead only use new Flutter views and existing layers from the native side? Let’s see how we can embed the Flutter view into the codebase of an existing app.
Example of existing application
We can make a simple example of an app with a login screen with MVVM attached right away from Android Studio templates.
In your IDE, select File>New>New project… and choose a template.
Then, finish a project creation by providing data to the creator.
The created project should have a structure like this:
As you can see, we got a login screen view activity with the ViewModel and its models in the ui.login package. We also got a login repository with its data source and models in the data package. We won’t focus in the series on every aspect of that generated code. We want to replace our “old” view with the Flutter one, so we will focus only on UI-related stuff.
A new Flutter app module
We need to provide a new login view. How can we add Flutter code to the existing code base?
I would recommend reading the whole Flutter documentation page to be aware of all aspects of embedding Flutter into android apps.
There are two ways of adding a new Flutter module.
Manually, which can be checked here, or by Android Studio wizard. Let’s stick with the wizard just to avoid errors.
In your IDE, select File>New>New module… and choose type.
See also: How I learned to use Flutter? Tutorial
Fill in the wizard form with data. By default, the wizard will set your Project location to be separate from your native project directory. That is something that you should consider in real apps in case of decoupling native (including iOS) and Dart code. In our example, I’ll place a module inside the original project for the sake of simplicity. It’s because we are working with Android only.
Choose the package name.
After clicking Finish – we got our module (new_flutter_login) side by side with native one (app).
Mainly, it’s the main.dart file example screen.
A new login screen in Flutter
We want a “new login screen design” to be implemented in our Flutter app. Let’s create a separate screen file and fill it with a similar interface and logic, exactly like it’s done in the native app.
Check main.dart from the included example repo to see the root of the Flutter application. It contains theme colors to match native ones etc.
We are set, so now we need to embed this screen into an existing app.
See also: Flutter resources and other useful tools
Embedding into native app
There are multiple ways of showing Flutter content in existing apps. We can add Flutter as:
We will stick with embedding as a whole activity using FlutterFragmentActivity. Let’s create Activity in the same package as original Activity and just name it FlutterLoginActivity.
The next thing we want to do is to add our activity to the AndroidManifest.xml file like this.
Remember to add all of the <activity> attributes.
Start a new screen from the old one
To see the new Flutter screen we just need to open our FlutterLoginActivity with a prepared Intent. To prepare an Intent for custom activity, we need to extend one of the Flutter engine Intent builders from FlutterFragmentActivity. It needs to point to our Activity class while building the Intent. Then, we can expose this builder like it’s done in FlutterFragmentActivity and run it.
The results can be seen here.
As you can see – there is a black screen transition between the old screen and the new one. That’s because something needs to be displayed before the first frame of the Flutter screen is finally shown. Normally, it would be a white screen. But while recording videos on my phone, I’ve noticed that in the mode without bottom navigation controls, the screen is black. It’s probably some kind of bug on my One Plus phone. We can fix this behaviour by adding a custom Flutter splash screen with forced white background.
Flutter Splash screen example
To make a long story short:
- go to FlutterLoginActivity
- override provideSplashScreen method with provided white ColorDrawable to fit into screens transitions
You can use any drawables as your splash screen – read more about it here.
Result should look like this:
Did you notice quite a long transition time between the old and the new login screen? That’s because FlutterFragmentActivity needs to start a new Flutter engine at the beginning of its life. This could resultin a short delay before rendering the first frame of the Flutter screen. To reduce that delay, we can use a pre-warmed engine.
Warming up Flutter engine
The application root seems like a good spot for warming up the engine. Let’s see how to do it:
As you can see, we need to create a new FlutterEngine and start its execution to keep it ready and running. The Default Dart entry point points to Flutter’s main() function which executes our runApp(MyApp()) in main.dart. So in our case, it will run the Flutter app right away and keep it ready for FlutterLoginActivity to show. At the end of initialization, we need to add an engine to cache with a specified key to identify it later on.
Using engine in FlutterLoginActivity
To use the prepared engine, we will change our custom Intent builder to accept it’s ID in FlutterLoginActivity.
Now, we can start it in native LoginActivity.
Let’s see if it runs better.
In a compartment, it runs a lot faster. Keep in mind that we are running code in the debug mode. It’s not fully compiled and optimized but still, we got great performance.
So let’s sum up what we’ve done In this part:
- sample login activity in Kotlin,
- a new Flutter module inside our project,
- a new Flutter login screen with navigation to it,
- improvement over the performance of transition between the old (Kotlin) and the new screen (Dart).
As you can see, embedding Flutter into the existing Android app is easy. We’ve proved in this part that it’s as simple as adding the new native Activity to our app. Flutter engines need some tweaks at the beginning like pre-warming and an additional splash screen. After that, it’s working perfectly. The transition between one screen to another is smooth and overall it still looks and feels like a native experience. But at the beginning of the article, I’ve mentioned that we want to reduce the cost of moving to a new technology and the cost of implementing new designs. Our example was moving from an old login screen to a new login screen which doesn’t have any logic at the moment.
In the next part, we will focus on reusing data logic from the native code to control flow from our new Flutter view. Stay tuned!
And if you’d like to sum up what you’ve learned in the first part, here’s the GitHub repository of this Flutter/Android project.