Flutter Platform Channels
In this, we will learn how platform channels can be used to communicate between Flutter and Native Platform. They are derived from the binary messaging foundation. This is the second part of the article “Flutter: Communicating with the Native Platform”.
Flutter’s platform channel allows Flutter to communicate with the native platform. These channels enable you to pass data back and forth between Flutter and the native platform.
Platform Channel is the construct that combines a channel name and a codec.For example, if we look at the MethodChannel code, we can see it combining the codec and the channel name.
MessageCodec describes a message encoding/decoding mechanism. It helps with serializing and deserializing messages sent between Flutter and native code. Flutter has four predefined MessageCodec implementations. They are:
- StringCodec: MessageCodec with UTF-8 encoded String messages. It is used for passing strings.
- BinaryCodec: MessageCodec with unencoded binary messages represented using ByteData. It is used if encoding/decoding is not needed.
- JSONMessageCodec: MessageCodec with UTF-8 encoded JSON messages. It is used to send JSON strings.
- StandardMessageCodec: MessageCodec using the Flutter standard binary encoding. It is the default MessageCode used by the channels. It’s a versatile and efficient codec that can handle various data types.
You can create your own custom MessageCodec implementation. You’ll have to implement compatible encoding and decoding in Dart, Java/Kotlin, and Objective-C/Swift.
Platform Channels
There are three types of platform channels in Flutter. They are:
- MethodChannel: It is used to invoke the method in the native platform and get some value in return. Flutter sends a method call to the native platform which processes it and returns some value. It is for invoking functions, passing data to the native platform, and expecting a response.
- EventChannel: It is used when you want to stream data continuously from the native platform to the flutter. It establishes a unidirectional channel from native code to Dart, allowing native code to send events or stream data to Dart asynchronously. It is helpful if you want to receive continuous data like sensor data, real-time updates, Wi-Fi connection state, and so on. It is for streaming data from the platform to Flutter.
- BasicMessageChannel: It is designed for sending and receiving asynchronous messages between Dart and native code in both directions. It can be used for continuous, bidirectional communication.
Enough with the definition, now let’s dive into the code.
MethodChannel
It is a named channel for communicating with the Flutter application using asynchronous method calls.
In Dart:
import 'package:flutter/services.dart';
class MethodChannelCounter {
// Create a method channel with the channel name "methodChannelDemo"
static MethodChannel methodChannel = const MethodChannel('methodChannelDemo');
// Define a method to increment the counter on the native side
static Future<int> increment({required int counterValue}) async {
// Invoke the 'increment' method on the native side with the 'count' argument
final result = await methodChannel.invokeMethod<int>('increment', {'count': counterValue});
// Return the result received from the native side
return result!;
}
// Define a method to decrement the counter on the native side
static Future<int> decrement({required int counterValue}) async {
// Invoke the 'decrement' method on the native side with the 'count' argument
final result = await methodChannel.invokeMethod<int>('decrement', {'count': counterValue});
// Return the result received from the native side
return result!;
}
// Define a method to retrieve a random value from the native side
static Future<int> randomValue() async {
// Invoke the 'random' method on the native side
final result = await methodChannel.invokeMethod<int>('random');
// Return the result received from the native side
return result!;
}
// Define a method 'tryMe' (custom method) to interact with the native side
static Future<int> tryMe() async {
// Invoke the 'tryMe' custom method on the native side
final result = await methodChannel.invokeMethod<int>('tryMe');
// Return the result received from the native side
return result!;
}
}
Now, we can respond to the method called from the native side.
In Android:
//MethodChannel to handle communication between Flutter and the platform.
MethodChannel(flutterEngine.dartExecutor, "methodChannelDemo")
.setMethodCallHandler { call, result ->
//call represents the incoming method call from Flutter
//result represents result or response that you send back to Flutter after handling the method call.
// Retrieve the 'count' argument from the method call, if provided.
val count: Int? = call.argument<Int>("count")
// Determine which method was called from Flutter.
when (call.method) {
// Handle the 'random' method call.
"random" -> {
// Generate a random number between 0 and 100 and send it back to Flutter as a success result.
result.success(Random(System.nanoTime()).nextInt(0, 100))
}
// Handle the 'increment' and 'decrement' method calls.
"increment", "decrement" -> {
// Check if the 'count' argument is missing or invalid.
if (count == null) {
// If the 'count' argument is missing or invalid, send an error result to Flutter.
result.error("INVALID ARGUMENT", "Invalid Argument", null)
} else {
// If the 'count' argument is valid, perform the requested operation.
if (call.method == "increment") {
// Increment the 'count' and send the updated value to Flutter as a success result.
result.success(count + 1)
} else {
// Decrement the 'count' and send the updated value to Flutter as a success result.
result.success(count - 1)
}
}
}
// Handle any other method calls that are not implemented.
else -> {
// Send a "not implemented" result to Flutter.
result.notImplemented()
}
}
}
In iOS:
//FlutterMethodChannel to handle communication between Flutter and the platform.
FlutterMethodChannel(name: "methodChannelDemo", binaryMessenger: binaryMessenger)
.setMethodCallHandler { call, result in
//call represents the incoming method call from Flutter
//result represents result or response that you send back to Flutter after handling the method call.
// Retrieve the 'count' argument from the method call, if provided.
let count = (call.arguments as? NSDictionary)?["count"] as? Int
// Determine which method was called from Flutter.
switch call.method {
case "random":
// Generate a random number between 0 and 100 and send it back to Flutter as a result.
result(Int.random(in: 0..<100))
case "increment", "decrement":
if count == nil {
// If the 'count' argument is missing or invalid, send a FlutterError back to Flutter.
result(FlutterError(code: "INVALID ARGUMENT", message: "Invalid Argument", details: nil))
} else {
// If the 'count' argument is valid, perform the requested operation.
let updatedCount = call.method == "increment" ? count! + 1 : count! - 1
result(updatedCount)
}
default:
// Handle any other method calls that are not implemented.
result(FlutterMethodNotImplemented)
}
}
EventChannel
It is a named channel for communicating with platform plugins using event streams.
In Dart:
import 'package:flutter/services.dart';
class EventChannelTimer {
// Create the EventChannel with the specified name "eventChannelTimer".
static const _eventChannelCustom = EventChannel('eventChannelTimer');
// Create a method to get the timerValue stream.
static Stream<int> get timerValue {
// Use the receiveBroadcastStream method to create a stream of events from the platform side.
// Map the dynamic events to integers as they are received.
return _eventChannelCustom.receiveBroadcastStream().map(
(dynamic event) => event as int,
);
}
}
In Android:
// Create an EventChannel and set its stream handler.
EventChannel(flutterEngine.dartExecutor, "eventChannelTimer")
.setStreamHandler(CustomStreamHandler())
CustomStreamHandler
is a custom class that implements StreamHandler
interface.
class CustomStreamHandler : EventChannel.StreamHandler {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
private var isListening = false
private var counter = 0
// Code to set up and manage the event stream
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
if (events != null) {
isListening = true
scope.launch {
while (isListening) {
val value = counter++
withContext(Dispatchers.Main) {
// Send the value to Flutter as a success event.
events.success(value)
}
delay(1000) // Send value every 1000 milliseconds
}
}
}
}
override fun onCancel(arguments: Any?) {
// Set isListening to false to stop generating events.
isListening = false
}
}
In iOS:
//Event Channel
FlutterEventChannel(name: "eventChannelTimer", binaryMessenger: flutterViewController.binaryMessenger)
.setStreamHandler(CustomEventHandler())
CustomEventHandler
is a custom class that implements FlutterStreamHandler
class.
import Foundation
class CustomEventHandler: NSObject,FlutterStreamHandler{
private var counter = 0
private var timer: Timer?
// Code to set up and manage the event stream
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
events(counter)
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
// Send the value to Flutter as a success event.
events(self.counter)
self.counter += 1
}
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
timer?.invalidate()
timer = nil
return nil
}
}
BasicMessageChannel
It is a named channel for communicating with platform plugins using asynchronous message passing.
In Dart:
class BasicChannelImage {
// Declare a private static constant for the BasicMessageChannel
static const _basicMessageChannel =
BasicMessageChannel<dynamic>('platformImageDemo', StandardMessageCodec(),);
// Define a static method to request and receive an image from the platform
static Future<Uint8List> getImage() async {
// Send a message to request an image from the platform using the BasicMessageChannel.
final reply = await _basicMessageChannel.send('getImage') as Uint8List?;
// Check if the reply is null, indicating an error in loading the image.
if (reply == null) {
// If null, throw a PlatformException to indicate an error.
throw PlatformException(
code: 'Error',
message: 'Failed to load Platform Image',
details: null,
);
}
// Return the received image data as a Uint8List.
return reply;
}
}
In Android:
//BasicMessageChannel
BasicMessageChannel(flutterEngine.dartExecutor, "platformImageDemo", StandardMessageCodec())
.setMessageHandler { message, reply ->
//message represents the incoming message from Flutter
//result represents result or response that you send back to Flutter after handling the method call.
// Toast message indicating the received message from Flutter.
Toast.makeText(this, "Received message from Flutter: $message", Toast.LENGTH_SHORT).show();
// Check if the received message is "getImage."
if (message == "getImage") {
// Open the image file from the Android app's assets.
val inputStream: InputStream = assets.open("flutter.png")
// Read the image data from the input stream and send it as a reply.
reply.reply(inputStream.readBytes())
}
}
In iOS:
//BasicMessage Channel
FlutterBasicMessageChannel(name: "platformImageDemo", binaryMessenger: flutterViewController.binaryMessenger, codec: FlutterStandardMessageCodec.sharedInstance()).setMessageHandler{
(message: Any?, reply: FlutterReply) -> Void in
// Handle incoming messages from Flutter and reply to them
if(message as! String == "getImage") {
// Handle incoming messages from Flutter and reply to them
guard let image = UIImage(named: "flutter") else {
reply(nil)
return
}
// If the image is successfully loaded, encode it as bytes and reply with it
reply(FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 1)!))
}
}You can find my example project in Git Hub.
You can configure channels with any method codec, including custom ones.
You can find my example project in Git Hub.
In this article, we discovered how to use the platform channel to communicate between Flutter and Native platforms. Hope now you can write your platform channel to make your awesome Flutter project and plugins
Stay Curious and Follow to make sure to catch the next post.
Top comments (0)