preloader

One of the biggest challenges for mobile cross-platform frameworks is how to achieve the native performance of the application, and how to help developers create different kinds of features for different devices and platforms with as little effort as possible. In doing so, keeping in mind that UX should remain the same but with unique components that are specific for each particular platform (Android and iOS).

Platform Channel in Flutter - Benefits and limitations

Although cross-platform frameworks in most of the cases can resolve platform-specific tasks, there is a certain number of tasks that, with custom platform-specific code, can be achieved only through native. The question is, how those frameworks establish communication between the specific platform and application? The best example is Flutter’s Platform Channel.

Before we dive deeper into the Platform Channel (why it’s unique, how it’s implemented and what are the benefits and limitations of using it), it’s worth it to mention a few important things about why Flutter stands on top of the other cross-platform frameworks. Flutter is a relatively new framework that was first introduced at Google I/O 2017 and initially released in May 2017. It is an open-source framework resulting from the hard work of many Google experts in the past few years and drawn by their experiences with native Android. The framework is open-source, written in Dart (also open-source, and easy to learn). It’s the only cross-platform framework that can guarantee full native performance on both Android and iOS – Flutter code is compiled to native ARM machine code using Dart’s native compilers. Furthermore, Flutter is known for expressive and flexible UI with incredibly fast rendering (thanks to Google’s graphics engine SKIA). The other feature Flutter’s known for its fast development, mainly because of plenty of customizable built-in widgets and because of the magic of Hot Reload, which significantly reduces the amount of time needed to develop certain features. Anyway, the main focus of this blog and what we are currently most interested in is how Flutter offers a simple and easy way of communication with a native platform called Platform Channel.

What is Platform Channel and when should we use it?

As the Flutter community grows, more and more community plugins and packages that execute platform-specific functionalities appear. If your project requires a specific feature that is not supported in Flutter or it’s easier to implement it on the native side, you need to establish communication between native platforms (Android or iOS) and Flutter in order to execute that custom platform-specific code or call any API-s from native in Dart code. Platform Channel operates on the principle of sending and receiving messages, without code generation. The communication is bidirectional and asynchronous. The Flutter application (the portion of the app which is written in Dart) in this communication represents a client that sends messages to the host (Android or iOS) and expects a response back either as success or failure. When the message is received on the host side, we can execute necessary logic in native code (Java/Kotlin for Android or ObjC/Swift for iOS) or call any of the platform-specific API-s and send a response back to the Flutter application through the channel. When channels are created, it is mandatory to take care of naming. The name of the channel in the Flutter application needs to be the same as the one on the native side.

How to setup?

One of the basic characteristics of Platform Channel is the fact that it is easy to set up and understand both on the Flutter side, and the native side It’s also well documented and explained in the official documentation.

In order to explain how to setup/create a channel, I will create a simple example of communication between the Flutter app and Android native (Kotlin).

First, we need to create a channel in the Flutter app with an appropriate name. In this case, we can name it “platform_channel”:

static const MethodChannel _channel = const MethodChannel('platform_channel');

Then we need to create a channel on the Android side with the same name:

companion object {
 @JvmStatic
 fun registerWith(registrar: Registrar) {
 val channel = MethodChannel(registrar.messenger(), "platform_channel")
 channel.setMethodCallHandler(PlatformChannelPlugin(registrar.activity()))
 }
}

Once we create a channel, we need to create a method in the Flutter app in class PlatformChannel which will communicate with native:

static Future<String> dummy_func() async {
 String result = await _channel.invokeMethod('dummy_func');
 return result;
}

Now to get a response from this function and from native, we need to add this in the Flutter app where we want to collect this result:

static Future<String> getDummyFunc() async => await PlatformChannel.dummy_func();

And the final step is to provide an implementation for dummy_func in native:

override fun onMethodCall(call: MethodCall, result: Result) {
 when {
 call.method == "dummy_func" -> result.success(setupDummyFunc(call))
 else -> result.notImplemented()
 }
}
private fun setupDummyFunc(call: MethodCall): String {
 return "return dummy string from native"
}

With this piece of code, we can say that we established a basic communication between the Flutter app and native. Of course, this can be extended to provide any implementation that we need. If we want to pass arguments to native functions we can create a Map of values and pass it to invokeMethod as a second parameter:

Map<String, dynamic> args = <String, dynamic>{};
args.putIfAbsent("dummy1", () => “dummy1”);
args.putIfAbsent("dummy2", () => “dummy2”);
args.putIfAbsent("dummy3", () => “dummy3”);
static Future<String> dummy_func() async {
 String result = await _channel.invokeMethod(‘dummy_func’, args);
 return result;
}

Now we can access those values in native by their IDs:

dummy1 = call.argument<String>("dummy1").toString()
dummy2 = call.argument<String>("dummy2").toString()
dummy3 = call.argument<String>("dummy3").toString()

Here it is essential to mention that if we are planning to create complex communication between Dart and a platform-specific code, which involves the usage of complicated data structures, I strongly suggest using some mechanism for serializing structured data. Luckily, there is a simple solution for this provided by Google. It’s called Protocol Buffers.
Protocol buffers are platform and language-neutral mechanisms for data serialization. Even more, Google provided tutorials on how to use Protocol Buffers for the desired language.

Benefits of Platform Channel

When we are dealing with any communication, whether we are talking about communication between two or more apps, communication inside a single app, or as in our case, communication with Dart and native code, the logical question arises: is that communication safe and reliable? The Platform channel is secured, and that’s one of the important benefits that the Platform Channel offers. Within our process, there is a memory buffer for communication between Dart and native code, and there is not any interprocess communication required to establish this communication; thus, there is no way that any other outside process can access this communication. This means that Platform Channel has the same level of security as any other native Android or native iOS application.
One of the benefits of Platform Channel is communication, which is itself asynchronous and bidirectional, meaning that Platform Channel doesn’t block execution of other tasks that are independent of native code. Once the native code finishes its work, the result will be passed to the Dart, and the appropriate callback will be triggered and vice versa.
Another benefit is serialization and deserialization of values to and from messages that happen automatically when you send and receive values. This represents the valuable benefit of Platform Channel, alongside an ability to use Protocol Buffers for serializing structured data.

Limitations

Currently, the channel method can be called only from the UI thread (from the main Isolate). Calling MethodChannel and EventChannel from spawned Isolate is not possible at this moment. Performing long-running operations on the main thread can cause ‘junk’ on the Flutter application, and the platform side will block other message channels. Maybe it’s possible to create a workaround for this with ports, but the best advice is to avoid heavy lifting work on the main thread until Flutter resolves problems that happen during the call of platform channel methods from another Isolate.

Overview

Overall the Platform Channel represents a way to connect native code with the Flutter app (Dart). It can be used to implement any Flutter missing functionality using a platform-specific code (plugins) and call any APIs whether available in Java or Kotlin code on Android, or in Objective-C or Swift code on iOS. Moreover, it’s well documented and well described in the official documentation and a handy tool in cross-platform development.