Integrating a Flutter Screen into Your Native App

Flutter Logo
Learn how to integrate Flutter into an existing native Android application.

Some Background …

At Pelmorex, we don’t shy away from innovation and experimenting with new technologies. Once such technology is Flutter. Flutter is a framework developed by Google for creating cross platform mobile applications for iOS, Android, and even Web! Google defines it as a “…UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.”

At Pelmorex, we use Flutter within our existing iOS and Android “The Weather Network” apps to present users with a screen where they can subscribe for notifications.

The Notifications Screen

This screen is exactly the same on both apps so much of the development work was done only once. The integration into the apps, of course, has to be done for each platform. I’m going to walk you through how it was integrated into our Android app.

Android Integration

Integrating a single Flutter screen into an app is not particularly difficult. You can follow the steps here to get started: https://flutter.dev/docs/development/add-to-app/android/project-setup. You’ll see there are 2 options for bringing the Flutter project into the Android project. We went with option A, depending on the AAR using Gradle. We actually took it a bit further; we upload the Flutter project’s AARs to Artifactory and then import them using Gradle in our Android project. This way, a developer doesn’t actually need the Flutter code to run the Android project.

After that’s done, there are a handful of ways you can actually display the Flutter screen in your Android code described here: https://flutter.dev/docs/development/add-to-app/android/add-flutter-screen?tab=cached-engine-with-initial-route-kotlin-tab. We decided to use FlutterView as it gives us fine-grained control over the FlutterEngine. We also use multiple routes in the Flutter project, which allows us to support different features written in Flutter using the same FlutterEngine. I’ll get into those details later on. For now, here’s how we set up the FlutterView:

val engine = FlutterEngine(context)
engine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
engine.navigationChannel.pushRoute(“featureRoute”)

val channel = MethodChannel(engine.dartExecutor.binaryMessenger, “myMethodChannel”) 
val flutterView = FlutterView(this, FlutterSurfaceView(this, true))
findViewById<ViewGroup>(R.id.flutterViewContainer).addView(flutterView)

flutterView.attachToFlutterEngine(engine)
  • The MethodChannel is used to communicate with the Flutter screen
  • “featureRoute” is the route you want to navigate to on the Flutter end

In addition to this, you’ll need to make sure you’re handling the engine’s lifecycle events by calling the following functions at the appropriate times:

  • engine.lifecycleChannel.appIsResumed()
  • engine.lifecycleChannel.appIsPaused()
  • engine.lifecycleChannel.appIsDetached()
  • flutterView.detachFromFlutterEngine(engine)

Challenges

The greatest challenge that we were faced with was that we need to call some of our Flutter code from other screens of our app. So even when there’s no Flutter UI, we still need to be able to communicate with Flutter. We were able to do this by creating a separate MethodChannel and a separate route. The trick here is that the route used to communicate with no Flutter UI still needs to create a Flutter widget. Here’s what the code for that widget looks like: 

class EmptyWidget extends StatefulWidget {
 @override
 State<StatefulWidget> createState() => EmptyViewState();
}

class EmptyViewState extends State<EmptyWidget> {
 final MethodChannel _methodChannel = MethodChannel("myBackgroundMethodChannel");

 @override
 void initState() {
   _methodChannel.setMethodCallHandler((call) => {
     // Handle incoming function calls
   });
   super.initState();
 }

 @override
 Widget build(BuildContext context) {
   return Container(color: Colors.transparent);
 }
}

And as part of the main widget:

routes: {
 '/': (BuildContext context) => EmptyWidget(),
 '/featureRoute: (BuildContext context) => FeatureWidget()
}

Now I can create a MethodChannel on the Android side with the same name: “myBackgroundMethodChannel”. As long as we’re on the default route, we can communicate with the Flutter code at any time. We use this to create notification toggles elsewhere in the app even if the Flutter screen is not present.

Notification Toggle
Notification Toggles that can communicate with Flutter code

The use of Flutter started off as an experiment for us, but now that it’s being used within an iOS and Android app, we’re really starting to see it’s potential. It’s improved a lot since it’s early days, especially when adding to an existing app. If you haven’t given it a shot, I’d recommend you do!  

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like