Flutter is an amazing framework, extremely user-friendly and always a pleasure to use. That being said, there might come a time when you might want to integrate native Android/IOS code with Flutter or simply want to pass messages from Flutter to the native side. Whatever the reason may be, one thing is for sure, Flutter makes it very easy to accomplish this.

Flutter allows you to call platform-specific APIs (that are written in the OS native language) with the use of Platform Channels. Messages are passed from Flutter to the host operation system (either Android or IOS) where it is received and an action is carried out. Once it is complete you can send the success or error result back up to Flutter(A way to think of this is like a server. Think of Flutter being the client and the native side being the host/server. Flutter makes a request and the host processes it and then sends a response). An important thing to note is the name of the platform channel you set. The name of the platform channel you set in Flutter must match that of the one you set on the native side! We will look at this later during the coding part of this post but it is something to look out for. If for some reason you are not getting this to work, 99% of the time it is because of this.

With some of the background information out of the way, let’s get to coding.

I am going to create a new Flutter app and create a simple UI consisting of some text and a button. I am not going to cover how to build the UI in this post. If are unfamiliar with Flutter and would like a quick intro to the framework, then you should check out this post.

Here is the code for the UI

The result produced by the above code should look like this

Now the fun part, time to dive into native platform interactions. First, there are a few things we must do. Import the services package from Flutter and the async package from Dart.

Great! With the services package imported, we can begin to set up Flutter for making a native request. Under the class declaration, we will create a static const variable called platformMethodChannel and set it equal to a MethodChannel.

Note the name given to the MethodChannel, this is very import. As mentioned earlier, the name given in Flutter must match that of the one we will give in the native side.

With our Method Channel built, time to build the function that will make the request. Create a named async function doNativeSuff() .

Next, replace the temporary print statement from the onPressed of the raise button to the function created.

From

To

Awesome! Our function is now hooked up to our button. Lets finish building it out. Inside of the function, create a new variable called _message of type String. Then create another variable of type String called result . result should be a final. Set result equal to await platformMethodChannel.invokeMethod('changeLife');

Let us break down what is going on here. We are creating a new variable that is awaiting the completion of platformMethodChannel.invokeMethod . But you might be saying to yourself, "What on earth is that!". If we look at it closely, we can see that platformMethodChannel was defined earlier. It is the name given to the MethodChannel we created. Next there is .invokeMethod('changeLife'); . This is calls a method on this channel with the name 'changeLife' . 'changeLife' will be used on the native side to identify the call and to send the appropriate response. Please note that the string passed to .invokeMethod() must match on the native side or it will not work!

But what about error handling? What happens if there is an error? For this, it is best to wrap the call in a try-catch block. We can easily modify the above code to do this.

That’s what the code should now look like after adding the try and catch statements. It is pretty standard. The only strange addition would be the on PlatformException before the keyword catch . This is coming from the services package and the TL;DR explanation is, it is used to handle errors coming from the native side. In this example, we are just printing out the error.

There is one last thing we should do before moving on to coding the host side. We need to set the state with the result. Under the try catch block, use setState() to set the messaged recieved from the host to the variable nativeMessage we created earlier.

Your function should look like this

Onto the Native/Host side

For this example, I am going to be using Android. I don’t have a Mac so I can’t play with IOS :(. If you would like to learn how to do this on IOS, please see: https://flutter.io/platform-channels/#example-objc

Open the android folder of the project in Android Studio, then open MainActivity.java . First, we need to import a few things from the Flutter plugin. Add these lines to the top of your file.

With the necessary imports out of the way, we can move onto setting up the MethodChannel . Firstly create a variable CHANNEL and set it equal to the name given to the method channel in Flutter. I always recommend going to the dart file and copy and pasting the string to avoid any typos.

Next, we will create a new method channel. This takes two arguments, the FlutterView(), and the channel.

At the end of the MethodChannel, we will attach .setMethodCallHandler() which as you guessed handles the call. The .setMethodCallHandler() takes a new MethodCallHandler() . If you are using Android Studio then as you type, you should see it suggested by intellisense. (Thanks Flutter for the amazing plugin ;) ).

Your file should now look like this.

As shown above, we created the MethodCallHandler then overrode onMethodCall . onMethodCall runs when the method is called. It has two arguments, MethodCall and Result. The MethodCall has all information regarding the actual call such as being able to check the name, etc... Result handles what you send to Flutter.

Inside of onMethodCall , we will create a condition to check the name of the call, if it matches the name of the call specified, we will create a message with the value "Life Changed". Then we will use result to send the message to Flutter via the .success() method. If the name of the method call does not match the one specified, no action will be carried out. Note that this name Must match that of the one specified in Flutter.(This is the name passed to .invokeMethod() )

Here is how to code this

That’s it! With this few lines of code, Flutter and Android should be talking to each other. Recompile the app and give it a try. Here is a demo video of the final product

Diving Deeper

With the basics out of the way, it is really easy to expand on what you have learned. For example, if you would like to access a native API, you can do so. Simply write the code you want to be executed within the If statement and send the result back to Flutter.

For example here is a way to make your device vibrate from Flutter.

Add the relevant permissions to your AndroidManifest.xml file

Then modify the Dart and Java source as shown below.

Dart Code:

Android Code:

Time to recompile the app and see if it works. You may notice that I used the current API as well as the deprecated vibrate API. This was intentional. There might be some users who are still using old versions of Android so it is meant as an easy way to support them. If all goes to plan, the result should look something like this:

Console snapshot:

Conclusion

Today we learned the basics of using Flutter to communicate with native code. Being able to communicate with native API’s opens up many doors, maybe you want to use one of your favourite Java/Kotlin libraries with Flutter or simply want to open a link in a web-view or vibrate the user’s device. The possibilities are endless.

The full source code for this project can be found here: https://github.com/Nash0x7E2/Flutter-method-channel-tutorial. If you find any discrepancies please send me a message and I will rectify them as soon as I can.

This will be my last post for a while. My exams are starting next week and I need to focus my attention on it for the next few weeks.

— Nash