Flutter: Communicating with Native Platform

Flutter: Communicating with Native Platform

Table of contents

No heading

No headings in the article.

Flutter is the perfect tool for cross-platform apps. You can easily make a performant and beautiful app with Flutter. But in order to access the native platform API, you need to communicate with the native platform. Flutter can only understand Dart language and the native platform can only understand their respective programming language. So, how do Flutter and the native platform communicate with each other? It has to be a language of both Flutter and the native platform. So, what is used to communicate with each other?

Take a guess

Photo by Markus Winkler on Unsplash

If you guessed, binary then you guessed it right.

Flutter talks to the native platform by passing binary messages. To distinguish the messages, channels are used. So how do we send these binary messages across the platform?

We can make use of BinaryMessenger class to send messages across the platform.

BinaryMessenger class is the messenger class defined by the Flutter team which sends binary data across the Flutter platform barrier. This class also registers handlers for incoming messages.

The code below sends a binary message to the platform using BinaryMessenger through the channel ‘foo’.

import 'dart:convert'; 
import 'package:flutter/services.dart';
import 'dart:ui' as ui; 

class CustomBinaryMessenger {

  // A static method for sending a given value as a binary message.
  static Future<void> givenValue(String data) async {
    // Create a buffer to hold the binary data.
    final WriteBuffer buffer = WriteBuffer();

    // Convert the given data string into UTF-8 bytes.
    final List<int> utf8Bytes = utf8.encode(data);

    // Convert the UTF-8 bytes into an Uint8List.
    final Uint8List utf8Int8List = Uint8List.fromList(utf8Bytes);

    // Put the Uint8List into the buffer.
    buffer.putUint8List(utf8Int8List);

    // Get the final binary message data from the buffer.
    final ByteData message = buffer.done();

    // Send the binary message using the 'Messenger' class through chaneel `foo`.
    await Messenger().send('foo', message);

    return;
  }
}

// A custom implementation of the BinaryMessenger interface. I am only handling
// send here for the sake of example
class Messenger implements BinaryMessenger {
  @override
  // Handle incoming platform messages. In this case, it throws an unsupported error.
  Future<void> handlePlatformMessage(
      String channel, ByteData? data, PlatformMessageResponseCallback? callback) {
    throw UnsupportedError("This platform message handling is not supported.");
  }

  @override
  // Send a binary message to the platform using the 'ui.PlatformDispatcher'.
  Future<ByteData?>? send(String channel, ByteData? message) {
    // Use the 'ui.PlatformDispatcher' to send the platform message and handle the callback
    ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (data) {});

    return null;
  }

  @override
  // Set a handler for incoming messages. In this case, it throws an unsupported error.
  void setMessageHandler(String channel, MessageHandler? handler) {
    throw UnsupportedError("Setting message handler is not supported.");
  }
}

Now on Android, you can receive it using the code below:

class MainActivity : FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        // Configure the binary messenger to handle messages from Flutter.
        flutterEngine.dartExecutor.binaryMessenger.setMessageHandler("foo") { message, reply ->
        message?.order(ByteOrder.nativeOrder()) // Ensure proper byte order.
        val data = decodeUtf8String(message!!) // Decode the binary data to UTF-8 string.
        val x = message.toString() // Convert the message to a string for demonstration.
        // Display a Toast with the received message.
        Toast.makeText(this, "Received message from Flutter: $data", Toast.LENGTH_SHORT).show()
        reply.reply(null)
        }

        // Call the super method to finalize the FlutterEngine configuration.
        super.configureFlutterEngine(flutterEngine)
    }

    // Function to decode a ByteBuffer into a UTF-8 string.
    private fun decodeUtf8String(byteBuffer: ByteBuffer): String {
        return try {
            val byteArray = ByteArray(byteBuffer.remaining())
            byteBuffer.get(byteArray)
            String(byteArray, Charsets.UTF_8)
        } catch (e: Exception) {
            e.printStackTrace()
            ""
        }
    }
}

Similarly for IOS,

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      let flutterViewController = self.window.rootViewController as! FlutterViewController

      // Configure the binary messenger to handle messages from Flutter.
      let binaryMessenger = flutterViewController.engine!.binaryMessenger
      binaryMessenger.setMessageHandlerOnChannel("foo", binaryMessageHandler: { [weak self] message, reply in
          // Ensure proper byte order.
           guard let message = message else {
               reply(nil)
               return
           }
           // Decode the binary data to UTF-8 string.
           if let data = String(data: message, encoding: .utf8) {
               let x = message.debugDescription // Convert the message to a string for demonstration.
               // Display an alert with the received message.
               let alertController = UIAlertController(
                   title: "Message from Flutter",
                   message: data,
                   preferredStyle: .alert
               )
               alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
               flutterViewController.present(alertController, animated: true, completion: nil)
           }
           reply(nil)
      }
    )

      GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

    // Function to decode a ByteBuffer into a UTF-8 string.
    private func decodeUtf8String(byteBuffer: FlutterStandardTypedData) -> String? {
            let byteArray = [UInt8](byteBuffer.data)
            return  String(bytes: byteArray, encoding: .utf8)
    }
}

Communication is bidirectional, you can send from the native platform to Flutter. Make sure to implement setMessageHandler in our class Messenger to receive binary messages from the native platform and decode them. Just reverse the flow of our code and you can send: In IOS, using

// Send a binary message from platform to the flutter. It takes channel and message
// as params
flutterViewController.engine!.binaryMessenger.send(onChannel: <#T##String#>, message: <#T##Data?#>)

In Android, using

// Send a binary message from platform to the flutter.It takes channel and message
// as params
flutterEngine.dartExecutor.binaryMessenger.send()

Messages and responses are passed asynchronously, to ensure the user interface remains responsive.

Now, you can pass messages from Flutter to the native platform and vice versa. But, as you can see working with binary messages we have to worry about encoding, decoding, and handling the register, and so on. It can lead to verbose code and increase the complexity of the code. So, what is the solution?

Platform channel: Making the above process easier leads to the platform channel. A platform channel is a construct that combines a channel name and a codec. This pairing enables the conversion of messages into binary format for transmission and facilitates their conversion back from binary format upon reception. It makes working with the native platform a lot easier.

Have a look at one of the Platform channels named MethodChannel. If you look at the code, you can see it handling its own BinaryMessenger .

In my next post, we will look at the Platform channel and how to use it to pass messages from Flutter to the native platform and vice versa.


Stay Curious and Follow to not miss the next post.