As promised in my last post about the hot topics at I/O 2018, I wanted to take a deeper look at Flutter and here it is!

Cross platform frameworks such as Flutter sound quite promising. Two platforms with just one code base! However, I still remain a bit scared about a time coming when you are limited by their SDKs and you are not able to fully develop what is in your mind, or take advantage of the latest new things native development offers (take Jetpack as an example).

So, I thought one of the first things I would like to know more about is how easy it is to integrate your Flutter code with a native component, if you at some point find yourself in the need of developing such feature for your app. Here you have a nice post about how to make “RPC”-like calls from Flutter to native code. In this project, I will take a step further and also call a client method from the native code.

In the documentation, the native code is referred to as the host and the Flutter part is the client.

This is my first experiment with Flutter, in this project I set up a very simple application that combines both cross platform development (with the Flutter SDK) and a native module (w̵r̵i̵t̵t̵e̵n̵ ̵o̵n̵l̵y̵ ̵i̵n̵ ̵A̵n̵d̵r̵o̵i̵d̵ ̵s̵o̵ ̵f̵a̵r̵,̵ ̵b̵e̵c̵a̵u̵s̵e̵ ̵m̵y̵ ̵k̵n̵o̵w̵l̵e̵d̵g̵e̵ ̵a̵b̵o̵u̵t̵ ̵i̵O̵S̵ ̵d̵e̵v̵e̵l̵o̵p̵m̵e̵n̵t̵ ̵i̵s̵ ̵n̵o̵n̵-̵e̵x̵i̵s̵t̵e̵n̵t̵, thanks to the community the iOS part is also there!). Since it is a first try, it might have plenty of things that can be improved. Pull requests are, of course, more than welcome!

Setup

I will assume that you have the Android SDK and Flutter installed on your computer. Otherwise, there are nice tutorials for both of them. I recently discovered the great utility of codelabs and I can only recommend them. They are a great way to get everything ready and started with Flutter. You can find them here.

If you followed the getting started guides, by now you will be able to start a new project in Android Studio by clicking File->New->New Flutter Project… And everything will be set.

After that, if you clean up the comments and modify the layout to have something really simple, you will end up having something similar to this:

class MyHomePage extends StatelessWidget {



@override

Widget build(BuildContext context) {

return new Scaffold(

appBar: new AppBar(

title: new Text(title),

),

body: new Center(

child: new Column(

mainAxisAlignment: MainAxisAlignment.center,

children: <Widget>[

new RaisedButton(

child: new Text('Show native view'),

onPressed: _showNativeView,

),

],

),

),

);

}



Future<Null> _showNativeView() async {}

}

With that, you will be able to run the app and see a button that does nothing so far until we fill up the corresponding method.

Platform channels

Here starts the crux of the matter. We will now create a platform-channel which is the element Flutter offers us to communicate host (native code) and client side (Flutter). We have to give it a name to identify it on both sides.

In the client side we do something like:

static const platform = const MethodChannel(

'flutter.rortega.com.channel');

In the Android part we do:

class MainActivity() : FlutterActivity() {

companion object {

const val CHANNEL = "flutter.rortega.com.channel"

}



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

GeneratedPluginRegistrant.registerWith(this)



MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->

// manage method calls here

}

}

}

Tip: open a different instance of Android Studio to edit your native code. You can do this easily with Flutter tools, selecting “open Android module in Android Studio”. This way you will be able to see compilation errors and take advantage of all the sweetness Android Studio normally provides.

Then, as mentioned in the code comment, inside the methodCallHandler we receive the methods which also need to have the same string identifier on both sides of the platform channel. In my case, I just pretend to show a native view that is specific to Android:

if (call.method == "showNativeView") {

val intent = Intent(this, NativeViewActivity::class.java)

startActivity(intent)

result.success(true)

} else {

result.notImplemented()

}

Therefore, we need to call this method on the button pressed event in Flutter:

Future<Null> _showNativeView() async {

await platform.invokeMethod('showNativeView');

}

As simple as that, at this point, the app is able to navigate to a native view within the host code.

The other way around

As I said at the beginning, I also wanted to try calling a method exposed by the Flutter side from Android. That can be used e.g to deliver touch events or any kind of platform specific interactions that will be triggered from within the host code.

For that, we need to set a methodCallHandler also on the client side, like this:

MyHomePage({Key key, this.title}) : super(key: key) {

platform.setMethodCallHandler(_handleMethod);

}

This is how the _handleMethod looks like:

Dart tip: starting a method with underscore _ automatically turns it into a private one!

Future<dynamic> _handleMethod(MethodCall call) async {

switch(call.method) {

case "message":

debugPrint(call.arguments);

return new Future.value("");

}

}

Calling this method from the host code is this easy:

findViewById<Button>(R.id.button).setOnClickListener {

channel.invokeMethod("message", "Hello from native host")

}

Here are the changes for the communication in the direction towards the client. The logs display all the clicks we do on the button:

With this, we have one basic example of how to call methods both from Flutter to native and the other way around!

TL;DR

Communication between Flutter and modules written in native code is done in a seamless way through platform-channels. You can define your own channels and methods, both with unique string identifiers and pass information from one side to the other.

You can check the project here. I also encourage you to take a look commit by commit so you can get a better feeling of what is doing what.

Feel free to leave any comment, create an issue, raise pull requests or share your knowledge if you have done more complex communication Flutter-native.

Thanks for reading!