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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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:

  1. First, you return a StreamBuilder from build().
  2. You pass Connection.disconnected as the initial state for the widget, and networkStream as the actual Stream being used.
  3. In StreamBuilder‘s builder(), you get the event from the snapshot and convert it to color and user-readable string.
  4. 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:

First run after setting up event channels on Flutter

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:

  1. First, you’re sending a Constants.disconnected message to the eventSink callback whenever connectivity is lost.
  2. Then, you’re checking what the network state is in the onCapabilitiesChanged method. If the network has WIFI you send the wifi constant. If it has cellular, you send the cellular constant. Otherwise you send an unknown 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:

Show network connnectivity changes

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:

  1. Get a reference to the root ViewController and instantiate an EventChannel with the same name you used in Flutter and Android.
  2. 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.