Today, we are going to play with a really useful but quite ignored facility in the Flutter SDK, namely the EventChannel. It is a bridge between Dart and native code which is able to transmit recurring events without requiring multiple MethodChannel invokes from the receiving side. They are the best tool there is to implement listeners for app events occurring outside of Dart.
We are going to use our infamous playground project to bootstrap our implementation. Feel free to fork it and modify according to your needs. If you are interested in the details of how the playground works, please refer to this post.
Related Topics
Take a look at these posts to learn about common communication patterns between Dart and native code if you already know what you are looking for.
EventChannels are created pretty much the same way as method channels. Let’s create a dedicated file to store our singleton reference and give it a name to easily recognize it.
Our playground will request a subscription to this channel by providing a callback function. It will also eventually cancel its subscription when it is done working with the results. We explicitly alias function types for these behaviors like below.
public class MainActivity extends FlutterActivity {
private EventChannel channel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
// Prepare channel
channel = new EventChannel(getFlutterView(), "events");
channel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object listener, EventChannel.EventSink eventSink) {
startListening(listener, eventSink);
}
@Override
public void onCancel(Object listener) {
cancelListening(listener);
}
});
}
public class MainActivity extends FlutterActivity {
private EventChannel channel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
// Prepare channel
channel = new EventChannel(getFlutterView(), "events");
channel.setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(Object listener, EventChannel.EventSink eventSink) {
startListening(listener, eventSink);
}
@Override
public void onCancel(Object listener) {
cancelListening(listener);
}
});
}
In Java (or Kotlin), a single instance to EventChannel.StreamHandler interface grabs all listener request by itself. Remember that we sent an identifier parameter in Dart to be able to distinguish listeners from each other. Here, Object listener is exactly that, converted to a Java int.
Let’s define the remaining methods to complete our circle.
// Listeners
private Map<Object, Runnable> listeners = new HashMap<>();
// Listeners
private Map<Object, Runnable> listeners = new HashMap<>();
void startListening(Object listener, EventChannel.EventSink emitter) {
// Prepare a timer like self calling task
final Handler handler = new Handler();
listeners.put(listener, new Runnable() {
@Override
public void run() {
if (listeners.containsKey(listener)) {
// Send some value to callback
emitter.success("Hello listener! " + (System.currentTimeMillis() / 1000));
handler.postDelayed(this, 1000);
}
}
});
// Run task
handler.postDelayed(listeners.get(listener), 1000);
}
void cancelListening(Object listener) {
// Remove callback
listeners.remove(listener);
}
}
// Listeners
private Map<Object, Runnable> listeners = new HashMap<>();
void startListening(Object listener, EventChannel.EventSink emitter) {
// Prepare a timer like self calling task
final Handler handler = new Handler();
listeners.put(listener, new Runnable() {
@Override
public void run() {
if (listeners.containsKey(listener)) {
// Send some value to callback
emitter.success("Hello listener! " + (System.currentTimeMillis() / 1000));
handler.postDelayed(this, 1000);
}
}
});
// Run task
handler.postDelayed(listeners.get(listener), 1000);
}
void cancelListening(Object listener) {
// Remove callback
listeners.remove(listener);
}
}
This example simulates a timer that triggers each second. You may replace that behavior with a real listener to a native event source without changing the outline by copying the body of run() into your own block.
iOS Native
Flutter’s consistency in its own native APIs is really remarkable. Here, using Objective-C, we will glue all the pieces pretty much the same as Android.
Objective -C doesn’t support anonymous class instances. Therefore, we are going to define a FlutterStreamHandler in its own source file pair (.h/.m).
Native iOS classes for Flutter are always named as the same as their Android counterparts, with an added prefix of “Flutter”. Just like the same as before, we will store the listener id in a NSMutableDictionary to be able to map them back during cancellation. dispatch_after helps us define a timer like cycle to simulate a periodic event source. You may replace that with a real source of your choice.
To finish this up, let’s create our channel in our app delegate.
Boot up your emulator/simulator or plug your device to run your playground. If everything went smoothly, you now have an event source capable of listening events from the native platforms.
Source Code
You may find a complete implementation of this tutorial here. I hope it becomes useful. Until then, have a nice one!
What is TestFairy?
Watch our YouTube demo
Try TestFairy for Free
TestFairy enables companies develop faster and deliver better apps