The power of a Flutter module in a native App

Florian DUHEN
norsys-octogone
Published in
8 min readMar 17, 2022

As a Flutter Developer, you probably already heard/read about “How to call a native method from the Dart side”.
It may be because you had to use a native library, or because you developed a plugin after reading my first Story “Build your own Scalable Flutter Plugin

But what about the opposite ?

Is it possible to add a Flutter module to a native app, and use it as a “Common” part between my Android and iOS applications ?

The answer is YES, and it may be even easier than what you think.

Use Case

You are working for a big client who wants to add a few features to his native apps.
He wants to retrieve a LOT of datas from a public API, re-work those datas, and to save it in a local DB.

As a single developer, you’ll have to write the code… TWICE.
First for Android, and then for iOS… It would be an understatement to say that you’re not thrilled by this idea !

But.. what if I tell you that you could write down this code only once, and easily implement it on both platforms ?
Bienvenue to Flutter’s world !

TL;DR : What am I going to showcase ?

I’ll show you how to trigger Dart code from a Native app.
More specifically, we’re going to fetch Datas from an API with the Flutter module, and to send it back to our native apps.

In this example, our goal architecture is the following

Since a lot of articles already explain how to display a FlutterActivity in a native app, I won’t talk about this here. In this story, I’ll focus on “how to trigger Dart code from a native App”.

Let’s dive into the code !

First I’ll go through the Flutter module, and then I’ll showcase the Android implementation. Not being an iOS developer, I won’t be able to give you the proper guidelines on this OS.

Part 1 : Flutter Module

First things first, you should start by creating a Flutter Module.
To keep this article focused on our main topic, I’ll redirect you toward the official documentation to create a Flutter Module

Android Studio Bumblebee 2021.1.1 and the latest Flutter Plugin aren’t compatible, thus you’ll have to use the command line to create your module and not the IDE

The libraries I’ll use in this sample are the following

#Serialization
json_serializable: ^6.1.4
json_annotation: ^4.4.0
#Service Locator
get_it: ^7.2.0
#Network
retrofit: ^3.0.1
dio: ^4.0.4
http: ^0.13.4

Step 1 — Retrieving datas from an API

In order to retrieve those Complex Datas from our API I’ll use Retrofit and Dio
My Service class looks like the following

https://github.com/FDuhen/flutter_module_demo/blob/master/api_module_demo/lib/core/services/api_dummy.dart

And I created a “Backend Provider” whose job is to call my Service. It’s the place I’d re-work my datas if I had to.

https://github.com/FDuhen/flutter_module_demo/blob/master/api_module_demo/lib/core/providers/backend_provider.dart

In the code above, the ApiAccessor class is just a Singleton allowing me to access my Service. Check the Github Repo if needed.

And last but not least, my Dummy model looks like the following. I used JsonSerializable to generate the method allowing to serialize/deserialize it easily

https://github.com/FDuhen/flutter_module_demo/blob/master/api_module_demo/lib/core/models/dummy.dart

Note that I added a method called toRawJson().
This method is really important.
We’re gonna exchange datas between our Native part and the Flutter Module, and sadly we can’t send Objects that easily : we have to convert it to JSON first.
The method toRawJson() allows us to encode our Model in String.

Step 2 — Listening to Native Calls

Now that the API logic is setup, we have to wait for the native part to trigger it.
How will it be triggered ? Thanks to the MethodChannels !

If you don’t know what a MethodChannel is, you can think of it as a “Topic” if you ever worked with Firebase Notifications.
You can subscribe to a specific Topic, and listen to every message sent to the said topic.

I created a class called NativeEntrypoints to ease readability, and subscribed to a MethodChannel called “CHANNEL_DUMMY” (my ‘topic’ !).

Last thing to do, is to filter which “messages” we want to handle on the channel.
To do so, every call on the MethodChannel will have a Method Name. You can picture it as an identifier.
In the following example, I’m filtering the messages with a simple Switch case statement, and triggering my API call if the Method Name is “METHOD_GET_DUMMY”.

https://github.com/FDuhen/flutter_module_demo/blob/master/api_module_demo/lib/native_entrypoints.dart

Note the call of the toRawJson() method, allowing us to send back to our Native app the Dummy model encoded in JSON String format.

Step 3 — Exposing our entry point to the Native apps

This is the easiest step !

Now that our code is setup and ready to be triggered, we only have to instantiate our NativeEntrypoints class in the main() method of our module and… that’s it!

https://github.com/FDuhen/flutter_module_demo/blob/master/api_module_demo/lib/main.dart

We don’t even have to call the runApp() method or to load any Widget, since we’re not doing any visual rendering from the Flutter side in this story.

Congrats ! Your Flutter Module is now done and it can be used in your native apps !

Part 2 : Native Apps

The Flutter part is done, now let’s work on the native code

Step 1 — Adding the Flutter Module to your native app

Since the Google’s documentation is already well done, I won’t cover this part.
My only recommandation for your native app, is to depend on the module’s source code and not on a compiled file (.aar for Android) : it’ll ease your development.

Step 2 — Init of the Flutter Engine

The init of the Flutter Engine is really important.
This step is the one triggering the main() method we edited earlier in the Flutter Module’s source code : without it, your Method Channels won’t be registered.

In my sample, I’m setting up the Flutter Engine in my Hilt module, but you could also do it in an Application subclass (as shown here)

https://github.com/FDuhen/flutter_module_demo/blob/master/module-consumer--android/app/src/main/java/fr/norsys/moduleconsumerandroid/domain/di/RepositoryModule.kt

Note : The FULL_SCREEN_ENGINE_ID is just a String with a random value.

Step 3 — Calling a method hosted in your Flutter Module

Since the Flutter Engine is started, we can now call our Method Channel !

To get a reference to the MethodChannel, we simply have to create a MethodChannel object with the default messenger and the Key we defined in our Flutter Module (“CHANNEL_DUMMY”)

Now that we have a reference to our MethodChannel, we can call any Method linked to it.
In our case, we’ll call the “METHOD_GET_DUMMY” method !

To do so, there is a method available with the MethodChannel class which is invokeMethod.
The invokeMethod method takes three parameters :
- The identifier of the method called (Here “METHOD_GET_DUMMY”)
- The arguments if any (here, we don’t need any args, we’ll juste use a placeholder)
- A Callback (MethodChannel.Result), which is an interface of three methods (Success/Error/NotImplemented)

In this example, I’m using a suspendCoroutine to trigger the invokeMethod and to handle the Callback as if it was a simple flow instead of an interface.

https://github.com/FDuhen/flutter_module_demo/blob/master/module-consumer--android/app/src/main/java/fr/norsys/moduleconsumerandroid/data/FlutterBridgeImpl.kt

So what’s happening here ?
1- A message is sent through the MethodChannel with the invokeMethod
2- This message which contains the Identifier “METHOD_GET_DUMMY” is caught by the Flutter module (in our super switch case statement !)
3- The Flutter module triggers the API Call with Retrofit
4- In case of success, it retrieves our Dummy Object, encodes it in JSON, and sends it back through the MethodChannel
5- The Callback (MethodChannel.Result) is triggered in the native Application
6- In case of success, and a non-null result, we parse it with Gson to re-construct our model

And.. That’s it !
With those few steps, you successfully triggered a Dart method from a Native call !!!

I hope that I unlocked a new vision of Flutter in your mind with this Story, and that it’ll help someday on one of your projects !

All the source code displayed here is available on my Github.
Feel free to use it !
https://github.com/FDuhen/flutter_module_demo

What about iOS ?

Well the logic will be the very same.
The only reason I didn’t use iOS in this sample is because I don’t have enough knowledge of the language to provide some clear and production-ready code.

Opening

In this example, I showcased how to execute a Network call through a Flutter module, but there are a lot of things you could achieve with the same logic !

Handling your Database, writing your complex algorithms only once, holding all the values needed by your app to work Offline… those are a few things that come in my mind and that I may put in a Flutter module in my future native projects.

Useful links

Thank you if you reached this far in this article ! 😍
I hope that it’ll help you fully understand how stuff works in the amazing Flutter’s world.

I’m Florian DUHEN, mobile developer and Flutter enthusiast.
You can find me on Github and StackOverflow
I work in a French company, norsys, which can be followed on LinkedIn and Twitter.

Feel free to ask any question or add any note in the comment section, I’ll do my best to answer it all.

--

--