DEV Community

Safal Shrestha
Safal Shrestha

Posted on

Flutter Platform Channels

Image description

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:

  1. StringCodec: MessageCodec with UTF-8 encoded String messages. It is used for passing strings.
  2. BinaryCodec: MessageCodec with unencoded binary messages represented using ByteData. It is used if encoding/decoding is not needed.
  3. JSONMessageCodec: MessageCodec with UTF-8 encoded JSON messages. It is used to send JSON strings.
  4. 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.

Architectural overview: platform channels

Platform Channels

There are three types of platform channels in Flutter. They are:

  1. 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.
  2. 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.
  3. 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!;
  }
}
Enter fullscreen mode Exit fullscreen mode

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()
            }
          }
        }
Enter fullscreen mode Exit fullscreen mode

Android Method Channel Demo

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)
        }
    }
Enter fullscreen mode Exit fullscreen mode

iOS Method Channel Demo


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,
    );
  }

}

Enter fullscreen mode Exit fullscreen mode

In Android:

// Create an EventChannel and set its stream handler.
EventChannel(flutterEngine.dartExecutor, "eventChannelTimer")
    .setStreamHandler(CustomStreamHandler())
Enter fullscreen mode Exit fullscreen mode

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
    }

}
Enter fullscreen mode Exit fullscreen mode

Android Event Channel Demo

In iOS:

//Event Channel
FlutterEventChannel(name: "eventChannelTimer", binaryMessenger: flutterViewController.binaryMessenger)
    .setStreamHandler(CustomEventHandler())
Enter fullscreen mode Exit fullscreen mode

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
    }

}
Enter fullscreen mode Exit fullscreen mode

iOS Event Channel Demo


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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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())
        }
    }
Enter fullscreen mode Exit fullscreen mode

Android BasicMessageChannelDemo

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.
Enter fullscreen mode Exit fullscreen mode

iOS Basic MessageChannel Demo


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.

Previous Post
Flutter: Communicating with the Native Platform
Flutter is the perfect tool for cross-platform apps. You can easily make a performant and beautiful app with Flutter.

Top comments (0)