How to use native APIs in Flutter: Platform Channels [Part 1]

Mais Alheraki
Invertase
Published in
5 min readJul 13, 2022

Flutter is a cross-platform toolkit, yet it allows you the full power of native platform APIs in a very simple way. Even if you don’t have any past experience writing native code, be scared no more. In this guide, you will learn everything you need to start using native APIs from within your Dart or Flutter project.

Flutter and the hosting platform

There are a couple of concepts to understand about the different ways Flutter communicate with the host native platform.

Platform-aware Dart code

Dart and Flutter are aware of the host platform. This means you can write some platform-aware conditional code such as:

In the previous snippet, note the import dart:io. The io library in Dart isn’t available on web, hence there’s no Platform.isWeb type.

In Flutter, it’s encouraged to avoid importing dart:io whenever possible. You can write the previous code using Flutter libraries:

TargetPlatform doesn’t have a web type as well. To write a web-specific code, there’s a constant available only in Flutter, kIsWeb.

If we look into the implementation of this constant inside the Flutter foundation library:

const bool kIsWeb = identical(0, 0.0);

It’s making use of the fact that JavaScript doesn’t support doubles. Dart on web is compiled to JavaScript, thus it’s backed with the same types of objects. Doubles aren’t identical to integers in Dart on other platforms or on the server side. Read the full explanation in the Flutter API.

Communicate with native APIs

Flutter has a rich ecosystem with hundreds of packages that cover almost all essential use-cases. But, there might come a time where you don’t find a plugin you need on pub.

Let’s imagine a common use-case. A client wants to integrate with a 3rd-party provider. This provider doesn’t have support for Flutter yet, but they have an iOS and Android SDK. In such case, you will need to call these SDKs from Flutter.

Platform channels

The answer to the use-case from the previous section starts with Platform Channels.

From the official documentation:

Messages are passed between the client (UI) and host (platform) using platform channels.

A platform channel is a flexible messaging API between a Flutter app and the platform hosting the app. It’s how you can communicate with native and 3rd-party APIs.

There’re libraries with everything you need to define a channel and communicate back and forth on both Dart, and the hosting platform. For example, to define a channel by name between Dart and Swift (iOS):

⒈ The Dart side:

const _channelName = 'app.id.com/channel_name'; 
final _channel = const MethodChannel(_channelName);

⒉ The iOS side:

Snippet for setting up a MethodChannel in Swift.

Note the classes on both sides have a similar API. This makes it easier to have a very similar and familiar code on all platforms. So, even if you don’t know Kotlin, the following snippet have the same API as the previous from Swift:

Snippet for setting up a MethodChannel in Kotlin.

Supported platforms and native languages

Flutter supports 6 platforms. Similarly, platform channels are also available on all supported platforms except web. The following table shows the native languages you can use with each corresponding platform.

Native languages used on each platform in Flutter.

What about web?

Dart is compiled to JavaScript on web, therefore, there is no need for platform channels. The interoperability between Dart and JS makes it possible to call JS functions directly using package:js.

To read more about using JavaScript in Flutter web, read this excellent article by Goncalo Palma.

Supported data types and the standard messaging codec

When you call a method, you either send data (input), expect data (output), or both. This also applies on method channels. You can send some data when calling a method channel from Dart, and receive from the other side. The question here is: if communication is happening between two different languages, what data types are available?

Messages are serialized and deserialized using a standard message codec as simple JSON-like values such as booleans, numbers, Strings, byte buffers, Lists and Maps. This means:

  • You can’t send or receive complex typed objects, e.g. user-defined types.
  • The data is not type-safe, therefore you need to check for types and nulls on both sides of the channel.

Check the official documentation here for the full list of data types received on each platform and the corresponding type from the Dart side.

Setting up Platform Channels

Platform channels are uniquely identified by name. This name is a String, if you misspelled it in either side of the channel, you won’t be able to reach out to the other side.

There are 2 types of platform channels:

  • MethodChannel: a channel used to communicate with the platform using asynchronous method calls. Think of it as a Future.
  • EventChannel: a channel used to listen to event streams from the platform. Think of it as a Stream.

MethodChannel

To setup a method channel in Dart:

// Give it a name.
const _channelName = 'app.id.com/channel_name';
// Construct it.
final _channel = const MethodChannel(_channelName);

Once you have a MethodChannel object ready, you can call any method by name:

Future<int> result = _channel.invokeMethod<int>('getBatteryLevel', {
'key': value,
});

EventChannel

To setup an event channel in Dart:

// Give it a name.
const _channelName = 'app.id.com/channel_name';
// Construct it.
final _channel = const EventChannel(_channelName);

Then, start listening to events on the channel:

events.receiveBroadcastStream().listen(
(arguments) {
_handleChangesListener(arguments);
},
);

Each event you will receive will have the arguments sent from the platform side in the listener callback.

There is an important question you need to think about when setting up an event channel: when is the right time to start the platform stream and when to close it?

Streams consume resources if it’s not disposed when no longer needed. That’s why you need to only start one when you need it, and make sure to clean up after you’re done with it.

The right time depends on the purpose this event stream is serving. You might have some event that you want to start at the moment the app is attached to the platform, which is at the time a user launches it. Another event could be needed only when you manually subscribe to it at some point.

To sum it up, you can:

  • Start a stream in the first method called when Flutter is attached to the platform. There’s such a method in all the 5 platforms. For example, on iOS it’s the application() method in AppDelegate class.
  • Use a MethodChannel to call a platform method that will set a listener to a platform stream, then send events on the channel. This way you only start the stream when you need it.

What’s next? 🔥

This’s only the introduction. The next part will dive deeper into actual examples for writing platform channels on iOS and Android.

Follow us on Twitter, LinkedIn, and Youtube, and subscribe to our monthly newsletter to stay up-to-date.

--

--

Mais Alheraki
Invertase

Software engineer, Geek, Designer, Reader & always a Lifetime Learner!