In Flutter, you might encounter scenarios where you need to communicate with native platform code to leverage platform-specific functionalities or libraries. One powerful mechanism for this communication is the EventChannel. Unlike MethodChannels that provide a request-response style of communication, EventChannels are designed for streaming data from the native side to Flutter. This blog post delves into how to use EventChannel for streaming data from native code in Flutter.
What is an EventChannel?
An EventChannel is a communication channel in Flutter used to establish a continuous stream of data from the native platform (Android or iOS) to the Flutter app. It is particularly useful when you need real-time or continuous updates from the native side, such as sensor data, location updates, or real-time data streams.
Why Use EventChannel?
- Real-Time Data: Facilitates streaming real-time data from native to Flutter.
- Asynchronous Updates: Enables asynchronous updates without the need for constant polling.
- Efficient Communication: Provides an efficient way to handle continuous data flow compared to request-response mechanisms.
How to Implement EventChannel in Flutter
To implement EventChannel, you need to set up communication channels on both the Flutter side and the native side (Android or iOS).
Step 1: Flutter Side Implementation
First, define the EventChannel in your Flutter code and set up a stream to receive data.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NativeStreamScreen extends StatefulWidget {
@override
_NativeStreamScreenState createState() => _NativeStreamScreenState();
}
class _NativeStreamScreenState extends State<NativeStreamScreen> {
static const eventChannel = EventChannel('example.streaming.data');
StreamSubscription? _streamSubscription;
List<String> _dataStream = [];
@override
void initState() {
super.initState();
_startStreaming();
}
@override
void dispose() {
_stopStreaming();
super.dispose();
}
void _startStreaming() {
_streamSubscription = eventChannel.receiveBroadcastStream().listen(
(data) {
setState(() {
_dataStream.add(data.toString());
});
},
onError: (error) {
print('Error receiving data: $error');
},
onDone: () {
print('Stream closed.');
},
);
}
void _stopStreaming() {
_streamSubscription?.cancel();
_streamSubscription = null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Native Data Stream'),
),
body: ListView.builder(
itemCount: _dataStream.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_dataStream[index]),
);
},
),
);
}
}
In this Flutter code:
- We define an
EventChannelwith a unique name (example.streaming.data). - We use
receiveBroadcastStream()to create a stream from theEventChannel. - We listen to the stream and update the UI with the received data.
- Error and done handlers are included to manage stream events.
Step 2: Android Native Code Implementation
Next, implement the native side (Android) to stream data to the Flutter app.
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import android.os.Handler
import android.os.Looper
class MainActivity: FlutterActivity() {
private val eventChannelName = "example.streaming.data"
private var eventChannel: EventChannel? = null
private val handler = Handler(Looper.getMainLooper())
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
eventChannel = EventChannel(flutterEngine.dartExecutor.binaryMessenger, eventChannelName)
eventChannel?.setStreamHandler(
object : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null
private var counter = 0
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
eventSink = events
startStreaming()
}
override fun onCancel(arguments: Any?) {
eventSink = null
stopStreaming()
}
private fun startStreaming() {
handler.post(object : Runnable {
override fun run() {
if (eventSink != null) {
eventSink?.success("Data from Native: ${counter++}")
handler.postDelayed(this, 1000) // Send data every 1 second
}
}
})
}
private fun stopStreaming() {
handler.removeCallbacksAndMessages(null)
}
}
)
}
override fun onDestroy() {
super.onDestroy()
eventChannel?.setStreamHandler(null)
}
}
In this Android code:
- We initialize the
EventChannelwith the same name used in Flutter (example.streaming.data). - We implement
StreamHandlerto manage the stream lifecycle. - The
onListenmethod starts streaming data when the Flutter side starts listening. - The
onCancelmethod stops streaming data when the Flutter side stops listening. - We use a
Handlerto send data every 1 second (you can adjust the interval as needed).
Step 3: iOS Native Code Implementation
Here’s the native implementation for iOS using Swift:
import Flutter
import UIKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private var eventChannel: FlutterEventChannel?
private var eventSink: FlutterEventSink?
private var timer: Timer?
private var counter = 0
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let binaryMessenger = controller.binaryMessenger
eventChannel = FlutterEventChannel(name: "example.streaming.data", binaryMessenger: binaryMessenger)
eventChannel?.setStreamHandler(self)
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
eventSink = events
startStreaming()
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
stopStreaming()
return nil
}
private func startStreaming() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
if let sink = self.eventSink {
sink("Data from Native: (self.counter)")
self.counter += 1
}
}
}
private func stopStreaming() {
timer?.invalidate()
timer = nil
}
}
In this iOS code:
- We initialize the
FlutterEventChannelwith the same name used in Flutter (example.streaming.data). - We implement
FlutterStreamHandlerto manage the stream lifecycle. - The
onListenmethod starts streaming data when the Flutter side starts listening. - The
onCancelmethod stops streaming data when the Flutter side stops listening. - We use a
Timerto send data every 1 second.
Conclusion
Using EventChannel in Flutter allows you to efficiently stream data from native code to your Flutter app, making it ideal for real-time updates and continuous data streams. By implementing the necessary setup on both the Flutter and native sides, you can leverage platform-specific functionalities while keeping your UI reactive and up-to-date.