An In-Depth Dive Into Streaming Data Across Platform Channels on Flutter
In this tutorial, you’ll learn how to use Platform Channels to stream data into your Flutter app. By Wilberforce Uwadiegwu.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
An In-Depth Dive Into Streaming Data Across Platform Channels on Flutter
25 mins
- Getting Started
- Platform Channels: An Overview
- Setting Up Event Consumption in Dart
- Reacting to Streamed Events
- Event Dispatching on Android
- Setting Up the EventChannel
- Dispatching Events
- Tearing Down the EventChannel
- Event Dispatching on iOS
- Setting Up EventChannel
- Dispatching Events
- Tearing Down the EventChannel
- Streaming Arbitrary Bytes
- Receiving, Concatenating and Consuming Bytes on Flutter
- Streaming Bytes on Android
- Streaming Bytes on iOS
- Where to Go From Here?
Reacting to Streamed Events
Now it’s time to update the UI. You’re going to use a StreamBuilder
since you’re operating on a Stream
returned from eventChannel
.
Still in NetworkStreamWidget
, replace the returned widget in build()
with:
@override
Widget build(BuildContext context) {
...
return StreamBuilder<Connection>(
initialData: Connection.disconnected,
stream: networkStream,
builder: (context, snapshot) {
final connection = snapshot.data ?? Connection.unknown;
final message = getConnectionMessage(connection);
final color = getConnectionColor(connection);
return _NetworkStateWidget(message: message, color: color);
},
);
}
With the statements above:
- First, you return a
StreamBuilder
frombuild()
. - You pass
Connection.disconnected
as the initial state for the widget, andnetworkStream
as the actualStream
being used. - In
StreamBuilder
‘sbuilder()
, you get the event from thesnapshot
and convert it to color and user-readable string. - Finally, you pass these values to
_NetworkStateWidget
._NetworkStateWidget
builds the widgets necessary to display the network states.
Run the project. You’ll something like this:
It’s starting to look good, but it’s not reactive yet. Also, a MissingPluginException
error appears in the log, but that’s expected for now.
Now that you have everything all wired up on the Dart side, it’s time to
tackle event dispatching on Android.
Event Dispatching on Android
Since you’re going to be working with native android code, you’ll write this section in Kotlin. You’ll set up an EventChannel with the same name you used earlier. Then you’ll listen for network state changes and notify the event callback of the EventChannel when the network state changes.
Setting Up the EventChannel
The first step is to create an EventChannel
in native Android code to mimic the one you created in Dart.
In the starter project directory, open the android folder with Android Studio. Now open the MainActivity
class and declare the channel name above configureFlutterEngine()
:
private val networkEventChannel = "platform_channel_events/connectivity"
Next, in configureFlutterEngine()
, right below the call to super
add:
EventChannel(flutterEngine.dartExecutor.binaryMessenger, networkEventChannel)
.setStreamHandler(NetworkStreamHandler(this))
And then import io.flutter.plugin.common.EventChannel
.
The above code creates a new EventChannel
with the channel name you specified in the last step. It uses the default BinaryMessenger
provided by the flutter engine and then sets a StreamHandler
to facilitate the actual event stream.
You’re done with MainActivity
for now. Good job! You’re killing it!👏
Now open NetworkStreamHandler
, and declare the event callback above onListen()
like this:
private var eventSink: EventChannel.EventSink? = null
An EventSink
is just a fancy callback.
Inside onListen()
, assign eventSink
and call startListeningNetworkChanges()
:
eventSink = events
startListeningNetworkChanges()
To save you some time, startListeningNetworkChanges()
is already implemented for you. It gets a reference to Android’s connectivity manager and registers networkCallback
. networkCallback
receives changes to the network state.
Next, you’ll work on dispatching connectivity events.
Dispatching Events
Now you’ll declare constants for the supported network states in Android, just like you did in Flutter.
In the Constants.kt file, add these statements inside Constants
:
const val wifi = 0xFF
const val cellular = 0xEE
const val disconnected = 0xDD
const val unknown = 0xCC
The actual event dispatching to Flutter happens in networkCallback
inside NetworkStreamHandler
. Override onLost()
and onCapabilitiesChanged()
like this:
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onLost(network: Network) {
super.onLost(network)
// Notify Flutter that the network is disconnected
activity?.runOnUiThread { eventSink?.success(Constants.disconnected) }
}
override fun onCapabilitiesChanged(network: Network, netCap: NetworkCapabilities) {
super.onCapabilitiesChanged(network, netCap)
// Pick the supported network states and notify Flutter of this new state
val status =
when {
netCap.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> Constants.wifi
netCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> Constants.cellular
else -> Constants.unknown
}
activity?.runOnUiThread { eventSink?.success(status) }
}
}
Make sure to add the import: import android.net.Network
.
The above code may be intimidating, but it’s actually pretty simple. Here’s a breakdown:
- First, you’re sending a
Constants.disconnected
message to theeventSink
callback whenever connectivity is lost. - Then, you’re checking what the network state is in the
onCapabilitiesChanged
method. If the network has WIFI you send thewifi
constant. If it has cellular, you send thecellular
constant. Otherwise you send anunknown
constant.
Note that the OS calls both onLost
and onCapabilitiesChanged
in the background. Since Flutter requires you to send platform channel events on the main thread, runOnUiThread
is used.
Next, you’ll clean up after yourself by tearing down the EventChannel.
Tearing Down the EventChannel
To shut down the event channel, you need to stop listening to network changes and nullify the fields.
Still in NetworkStreamHandler
, call the following statements inside onCancel()
:
stopListeningNetworkChanges()
eventSink = null
activity = null
Like startListeningNetworkChanges()
, stopListeningNetworkChanges()
is already implemented for you.
Run the project on Android. Toggle Wifi and Mobile Data and you’ll see the UI update:
That’s all for Android! Time to head over to iOS.
Event Dispatching on iOS
Now that you’re in iOS land, you’ll write this section in Swift with Xcode.
In the ios folder in starter project open Runner.xcworkspace with Xcode.
As you did for Android, you’ll set up an EventChannel with the same name as Flutter. Then, you’ll listen for network state changes and dispatch the events to Flutter.
First, you’ll set up the EventChannel.
Setting Up EventChannel
Inside AppDelegate.swift, declare the channel name above application()
in AppDelegate
:
private let networkEventChannel = "platform_channel_events/connectivity"
You’ll be using Reachability, a popular iOS library, to listen for changes to network states.
Still in AppDelegate
, paste these statements inside application()
, above GeneratedPluginRegistrant.register(with: self)
:
let controller = window?.rootViewController as! FlutterViewController
FlutterEventChannel(name: networkEventChannel, binaryMessenger: controller.binaryMessenger)
.setStreamHandler(NetworkStreamHandler(reachability: reachability))
In the statements above, you:
- Get a reference to the root
ViewController
and instantiate anEventChannel
with the same name you used in Flutter and Android. - Then pass an instance of
NetworkStreamHandler
as the stream handler to the EventChannel.NetworkStreamHandler
is responsible for dispatching the events to Flutter.
Next up you’ll dispatch the actual connectivity events.
Dispatching Events
Once again, in Constants.swift add the following constants as members of Constants
:
static let wifi = 0xFF
static let cellular = 0xEE
static let disconnected = 0xDD
static let unknown = 0xCC
Now, open NetworkStreamHandler.swift. Then declare the event callback right below the Reachability variable in NetworkStreamHandler
:
private var eventSink: FlutterEventSink? = nil
Next, you’ll set up Reachability in NetworkStreamHandler
. First, create a function that Reachability calls when the network state changes. In this function, you’ll use switch
to check the state and send the appropriate event to Flutter.
Below onCancel()
add:
@objc func connectionChanged(notification: NSNotification) {
let reachability = notification.object as! Reachability
switch reachability.connection {
case .wifi:
eventSink?(Constants.wifi)
case .cellular:
eventSink?(Constants.cellular)
case .unavailable:
eventSink?(Constants.disconnected)
case .none:
eventSink?(Constants.unknown)
}
}
Now, you need to tell Reachability, “Hey, buddy, call connectionChanged
when you detect a network state change :]”.
Inside onListen()
, right above the return statement, add:
eventSink = events
NotificationCenter.default.addObserver(self, selector: #selector(connectionChanged(notification:)), name: Notification.Name.reachabilityChanged, object: reachability)
do {
try reachability.startNotifier()
} catch {
return FlutterError(code: "1", message: "Could not start notififer", details: nil)
}
Here you assign eventSink
and observe network changes with Reachability.
Now, you’ll tear down the EventChannel
.