Why you need platform channels to access native code in Flutter and how to use it

Alperen Ekin
HardwareAndro
Published in
5 min readJul 23, 2021

Native languages are always an important part of mobile programming. Even though Flutter is growing day by day, it is not always possible to achieve features we wish or achieve them efficiently. Also, there are some packages available in native side but do not exist in Flutter. Because of this reason, we can benefit from the power of platform channels and call native code from Flutter. In one of the projects I involved, It was necessary to use a java package, and we used platform channels to handle it. In another project, we needed to access data stored in a smart watch, and the watch had an android SDK, which has been prepared in Kotlin. Because of that, using platform channels was mandatory to make it work.

Fluter provides the flexibility and allows you to call platform specific code exists in Kotlin or Java in Android and in Swift and Objective-C code in iOS.

  • The Flutter side of the application makes a call as a client to a host which is iOS or Android side of the application.
  • Android or iOS side listens the platform channel, receives the message and do the operation in the native side. At the end, the respond is sent to the client.
  • The message and response are sent asynchronously.
The illustration prepared by Flutter’s documentation

Which types of data can be sent and their counterpart values are also explained in the documentation with the table below.

Now, let’s take a look at an example to enhance our understanding and how to use platform channels properly💪🏼

Getting Our Hands Dirty

We will make an example application which uses platform channels to decide in Java side whether a text is a palindrome or not. There will be a simple UI to make the call with different texts. Therefore, we will also observe how to pass a parameter to native side as well.

  • Note: — A palindrome is a word, number, phrase, or other sequence of characters which reads the same backward as forward, such as madam.

Implementation

Firstly, we create a new channel by using Flutter’s method channel.

class PlatformChannelHandler{
static PlatformChannelHandler _instance = PlatformChannelHandler._init();

static PlatformChannelHandler get instance => _instance;

PlatformChannelHandler._init();

static const platform = const MethodChannel('sampleChannel');

A singleton handler class is responsible for creation of the channel, and it will be responsible for making calls to the host. We should be careful about the channel name that is given with the constructor. It should be a unique name since it connects our application to native side. We will use the channel name in the future while defining the channel for Java side.

Secondly, we should define a method that will direct us to native side and allow it to do the job.

Future<bool> isInputTextPalindrome(String text) async {
bool isPalindrome;
try {
isPalindrome = await platform.invokeMethod('isInputPalindrome',{'text': text});

} on PlatformException catch (e) {
isPalindrome = null;
}
return isPalindrome;
}

By using invokeMethod, we can process the call. The first parameter of that call defines the method for the Java side, as we did in the creation channel. The second parameter is used to send data with the call.

Moving to Native Side

In android folder, inside MainActivity.java file, we should do the update to add Java code. First of all, we need to define the method channel name and method channel itself, then the MethodCallHandler should be defined to handle calls from Flutter app.

private static final String CHANNEL = "sampleChannel";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(new FlutterEngine(this));
MethodChannel channel = new MethodChannel(getFlutterEngine().getDartExecutor().getBinaryMessenger(), CHANNEL);
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {

As I mentioned above, the channel name must be same as the one we defined in Flutter side. Of course it is possible to define more than one channel with a unique name in case you need different channels.

Lastly, we define the piece of code we want to call according to method name.

channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
if(methodCall.method.equals("isInputPalindrome")){
boolean isBreak = false;
String input = methodCall.argument("text");
if(input != null){
int i = 0, j = input.length() - 1;
while (i < j) {
if (input.charAt(i) != input.charAt(j)) {
result.success(false);
isBreak = true;
break;
}
i++;
j--;
}
if(!isBreak)
result.success(true);
}else{
result.error("UNAVAILABLE", "Paremeter is unavailable.", null);
}
}else{
result.notImplemented();
}
}
});

Again we must be sure that the method name matches with the one defined in the Flutter side of the application. The same situation is valid for the parameter. We can access the argument with the ‘argument’s property. At the end the result should be sent to Flutter side.

After designing a simple view in order to observe how it works:

Every time when a text is written and the button is clicked, it triggers to platform channel and the Java code checks whether the text is a palindrome or not.

Conclusion

I tried to make a simple example to explain the logic behind platform channels. The code in the native side can change but the way we use it will be same for the different examples. You may need to call a previously written native code from your Flutter app, or you may need a package that not exist in the Flutter but only in the native side. Also even you do not use platform channels directly. It is used in the packages like permission_handler and I believe It is good to have an idea about the packages you use.

I have not added all the code for simplicity in this article however you can reach the details from the link below.

Thank you for reading the article, see you in the next articles.👌🏻👌🏻

--

--