The introduction

We use plugin to realize the communication between the flutter end and the native end. It is mainly reflected in the mutual invocation of methods and the sending and monitoring of data flow. Today we’ll document the implementation of these two interactions: MethodChannel and EventChannel

  • MethodChannel: for method calls
  • EventChannel: Communication for Event streams, (listening, sending)

We need ademo

Create a simple demo page with three elements:

  • receiveDataText block: The listener displays the data stream received from the native end
  • startTimerButton: The flutter side calls the native side to enable event polling
  • stopTimerButton: The flutter side calls the native side to turn off event polling

In the demo, the Flutter key calls the Native layer method via methodChannel to enable Timer polling. The Native end sends the current timestamp back to the eventChannel data stream every second.

Starting Demo Creation

  • Native client:

Create a Plugin class that inherits FlutterPlugin

class PluginCommunicationDemoPlugin : FlutterPlugin.MethodCallHandler.EventChannel.StreamHandler {
    // Path definition for methodChannel and eventChannel through which communication takes place
    companion object {
        const val METHOD_CHANNEL_PATH = "rex_method_channel"
        const val EVENT_CHANNEL_PATH = "rex_event_channel"
    }

    private lateinit var methodChannel: MethodChannel
    private lateinit var eventChannel: EventChannel
    private var eventSink: EventChannel.EventSink? = null

    private var mWorker: TimeWorker = TimeWorker(object : TimerWorkCallback {
        override fun onResult(data: String) {
            sendEventToStream(data)
        }
    })

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, METHOD_CHANNEL_PATH)
        methodChannel.setMethodCallHandler(this)
        eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, EVENT_CHANNEL_PATH)
        eventChannel.setStreamHandler(this)}// methodChannel method call
    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        when (call.method) {
            "startTimer" -> {
                result.success(mWorker.startTimer())
            }
            "stopTimer" -> {
                result.success(mWorker.stopTimer())
            }
            else -> {
                result.notImplemented()
            }
        }
    }

    // Push messages to the Event stream. The flutter layer listens to the stream
    fun sendEventToStream(data: String) { Handler(Looper.getMainLooper()).post { eventSink? .success(data) } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        methodChannel.setMethodCallHandler(null) } override fun onListen(arguments: Any? , events: EventChannel.EventSink?) {// eventChannel establishes the connection
        eventSink = events
    }

    override fun onCancel(arguments: Any?) {
        eventSink = null}}Copy the code
  • onMethodCallmethodsmethodChannelEvent distribution, passresult.success(any)Sending event results
  • throughEventChannel.EventSinkInject data intoeventThe FLUTTER layer listens on the data stream
  • TimeWorkerControl of the polling start stop, the following attached specific code
class TimeWorker {

    private val format = SimpleDateFormat("yyyy-MM-DD HH:mm:ss")
    var timer: Timer? = null
    var callback: TimerWorkCallback? = null

    constructor(callback: TimerWorkCallback?) {
        this.callback = callback
    }

    // Start sending the event stream
    fun startTimer(): Boolean {
        stopTimer()
        if (timer == null) { timer = Timer() } timer? .schedule(object : TimerTask() {@RequiresApi(Build.VERSION_CODES.O) override fun run() { callback? .onResult(getCurrentTimeStr()) } }, Calendar.getInstance().time,1000)
        return true
    }

    // Stop the event streamfun stopTimer(): Boolean { timer? .cancel() timer =null
        return true
    }

    private fun getCurrentTimeStr(): String {
        return format.format(Calendar.getInstance().time)
    }

}

interface TimerWorkCallback {
    fun onResult(data: String)}Copy the code
  • FlutterClient:

Package methodChannel and eventChannel into utility classes:

import 'package:flutter/services.dart';

abstract class TestPluginTool {
  static const MethodChannel _methodChannel = const MethodChannel("rex_method_channel");
  static const EventChannel _eventChannel = const EventChannel("rex_event_channel");

  // Enable native polling
  static Future<bool> startTimer() async {
    return await _methodChannel.invokeMethod("startTimer");
  }
  // Turn off native polling
  static Future<bool> stopTimer() async {
    return await _methodChannel.invokeMethod("stopTimer");
  }
  // Listen for native event data streams
  static voidonListenStreamData(onEvent, onError) { _eventChannel.receiveBroadcastStream().listen(onEvent, onError: onError); }}Copy the code

Very simple, one-to-one, matching the paths of methodChannel, eventChannel

  • The final step is to complete the UI:

class _MyAppState extends State<MyApp> {
  var _receivedData = "";

  @override
  void initState() {
    super.initState();
    _onReceiveEventData();
  }
  
  // Enable polling
  void _startTimer() {
    TestPluginTool.startTimer().then((value) => print("startTimer success"));
  }

  // Close polling
  void _stopTimer() {
    TestPluginTool.startTimer().then((value) => print("stopTimer success"));
  }

  /// Listen for the eventChannel data stream
  void _onReceiveEventData() {
    TestPluginTool.onListenStreamData((data) {
      setState(() {
        _receivedData = data;
      });
    }, (error) {
      print("event channel error : $error");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text("receiveData : $_receivedData"),
          TextButton(
              onPressed: () {
                _startTimer();
              },
              child: Text("startTimer")),
          TextButton(
              onPressed: () {
                _stopTimer();
              },
              child: Text("stopTimer")),,),); }}Copy the code