Using more than one Event channels in Flutter

ยท

5 min read

Using more than one Event channels in Flutter

Hello there, it's been a minute ...

Introduction

All those seasoned Flutter/Android/iOS developers know that at the end of the day, Flutter apps are just android and iOS apps on the Android Platform and iOS platform respectively and if you didn't know now you know. So in order to access platform API(s) or Introduce new features through libraries available on Android or iOS, you need to write Platform channels that the Flutter engine can communicate using. So far they are three available channels: (1) Method Channels, (2) Event Channels, and the last one Native Views.

Method Channels, provide a way of communicating with Platform-specific code/APIs asynchronously, meaning if Flutter code calls a Platform code/API if the call returns a value the flutter code will have to async-wait the value. For more information on this please read this official documentation.

Native Views, Native views for lack of better words puncture the Flutter engine and renders a native View in Flutter, For example: using native views you can render an Android EditText or iOS UITextField. Interesting most popular flutter plugin using this method example the Official Flutter Google Maps plugin Link here to render Google maps.

Event Channels, and now to the order of the day, event channels also to keep things simple and stupid they are like streams now in this case, events can be sent from the Platform side to the Flutter side, like how normal stream work.

This article will talk about having more than one event channel in one code base, The motivation for this article is; I am involved in a project that challenged me while working with event channels because most online resources show you how to add one only.

Let's get to it

In this article, we shall create an example that will have two event channels one will emit the current timestamp and the other zero to infinity.

Create a new flutter project: flutter create two_eventchannels

Inside your project root directory open the android folder with Android Studio for better syntax highlighting and code completion. Wait for Gradle to do its thing ..... and yes we shall be writing some Kotlin

Your android studio should look something like this:

Screenshot from 2021-06-28 23-32-38.png

Open the MainActivity.kt file should have:

package com.example.two_eventchannels

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity() {

}

Let's declare our event channels:

...
class MainActivity: FlutterActivity() {
    private val eventChannel1 = "com.example.two_eventchannels/events1"
    private val eventChannel2 = "com.example.two_eventchannels/events2"
}
...

Then override configureFlutterEngine that comes from FlutterActivity

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        EventChannel(flutterEngine.dartExecutor.binaryMessenger, eventChannel1)
        EventChannel(flutterEngine.dartExecutor.binaryMessenger, eventChannel2)
    }

So that the code looks like ๐Ÿ‘†

Let create our stream handler, I shall start with the one for streaming current time:

object TimeHandler : EventChannel.StreamHandler {
    // Handle event in main thread.
    private var handler = Handler(Looper.getMainLooper())

    // Declare our eventSink later it will be initialized
    private var eventSink: EventChannel.EventSink? = null

    @SuppressLint("SimpleDateFormat")
    override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
        eventSink = sink
        // every second send the time
        val r: Runnable = object : Runnable {
            override fun run() {
                handler.post {
                    val dateFormat = SimpleDateFormat("HH:mm:ss")
                    val time = dateFormat.format(Date())
                    eventSink?.success(time)
                }
                handler.postDelayed(this, 1000)
            }
        }
        handler.postDelayed(r, 1000)
    }

    override fun onCancel(p0: Any?) {
        eventSink = null
    }
}

Basic we have declared a singleton class called TimeHandler that extends EventChannel.StreamHandler gives us two overrides onCancel and onListen which is of importance to us. Also, we have declared our OS handler and passed Looper.getMainLooper() so that the Handler can run on the UI thread and after a second we get the current time and sink it to our stream.

We shall do similar things with the Counter one:

object CounterHandler : EventChannel.StreamHandler {
    // Handle event in main thread.
    private val handler = Handler(Looper.getMainLooper())

    // Declare our eventSink later it will be initialized
    private var eventSink: EventChannel.EventSink? = null

    @SuppressLint("SimpleDateFormat")
    override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
        eventSink = sink
        var count: Int = 0
        // every 5 second send the time
        val r: Runnable = object : Runnable {
            override fun run() {
                handler.post {
                    count ++;
                    eventSink?.success(count)
                }
                handler.postDelayed(this, 1000)
            }
        }
        handler.postDelayed(r, 1000)
    }

    override fun onCancel(p0: Any?) {
        eventSink = null
    }
}

Now that is done before heading over to the dart world we need to set our stream handlers on the event channels we declared. After that your code should look like this:

class MainActivity : FlutterActivity() {
    private val eventChannel1 = "com.example.two_eventchannels/events1"
    private val eventChannel2 = "com.example.two_eventchannels/events2"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        EventChannel(flutterEngine.dartExecutor.binaryMessenger, eventChannel2).setStreamHandler(
            CounterHandler
        )
        EventChannel(flutterEngine.dartExecutor.binaryMessenger, eventChannel1).setStreamHandler(
            TimeHandler
        )
    }
}

object TimeHandler : EventChannel.StreamHandler {
    // Handle event in main thread.
    private var handler = Handler(Looper.getMainLooper())

    // Declare our eventSink later it will be initialized
    private var eventSink: EventChannel.EventSink? = null

    @SuppressLint("SimpleDateFormat")
    override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
        eventSink = sink
        // every second send the time
        val r: Runnable = object : Runnable {
            override fun run() {
                handler.post {
                    val dateFormat = SimpleDateFormat("HH:mm:ss")
                    val time = dateFormat.format(Date())
                    eventSink?.success(time)
                }
                handler.postDelayed(this, 1000)
            }
        }
        handler.postDelayed(r, 1000)
    }

    override fun onCancel(p0: Any?) {
        eventSink = null
    }
}

object CounterHandler : EventChannel.StreamHandler {
    // Handle event in main thread.
    private val handler = Handler(Looper.getMainLooper())

    // Declare our eventSink later it will be initialized
    private var eventSink: EventChannel.EventSink? = null

    @SuppressLint("SimpleDateFormat")
    override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
        eventSink = sink
        var count: Int = 0
        // every 5 second send the time
        val r: Runnable = object : Runnable {
            override fun run() {
                handler.post {
                    count ++;
                    eventSink?.success(count)
                }
                handler.postDelayed(this, 1000)
            }
        }
        handler.postDelayed(r, 1000)
    }

    override fun onCancel(p0: Any?) {
        eventSink = null
    }
}

Almost done

Now let's write some dart code that called these event channels

import 'package:flutter/services.dart';

Stream<String> streamTimeFromNative() {
  const _timeChannel =
      const EventChannel('com.example.two_eventchannels/events1');
  return _timeChannel.receiveBroadcastStream().map((event) => event.toString());
}

Stream<int> streamCounterFromNative() {
  const _counterChannel =
      const EventChannel('com.example.two_eventchannels/events2');
  return _counterChannel.receiveBroadcastStream().map((event) {
    return int.parse(event.toString());
  });
}

Without tickering with the boilerplate flutter create creates we shall edit the code to:

Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Timer :',
            ),
            StreamBuilder<String>(
              stream: streamTimeFromNative(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                } else {
                  return CircularProgressIndicator();
                }
              },
            ),
            SizedBox(height: 30),
            Text(
              'Counter :',
            ),
            StreamBuilder<int>(
              stream: streamCounterFromNative(),
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                } else {
                  return CircularProgressIndicator();
                }
              },
            ),
          ],
        ),
      ),
    );
  }

Then run your project: Source code can be found here: link

Demo: event .gif

Did you find this article valuable?

Support Patrick Waweru by becoming a sponsor. Any amount is appreciated!