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

Flutter’s Platform Channels are a set of APIs that facilitate calling native platform APIs from Dart. There are three main platform channel APIs that can be used to communicate back and forth with the underlying native technology: MessageChannel, MethodChannel and EventChannel. These APIs provide an interface to call methods and pass data between code written in Java/Kotlin or Objective-C/Swift and Dart.

Platform channels make it easy to integrate native functionality, like camera, geolocation and deep links, into Flutter apps. In this tutorial, you’ll use platform channels to stream predefined events and arbitrary data from iOS and Android to Dart. You’ll learn:

  • How to use Event Channels to stream connectivity information to your flutter app.
  • Using Event Channels to send images in the form of binary data to your flutter app.
  • Setting up both native Android and iOS clients to interact with your Dart code.

Getting Started

To get started, download the starter project by clicking Download Materials at the top or bottom of this tutorial.

Open starter with the latest version of Android Studio or Visual Studio. Then, run flutter packages get from Terminal. If you’re building for iOS, you’ll also need to install iOS dependencies by navigating to the iOS directory and running pod install.

Run the project. You’ll see something like this:

Starter project screenshot

In this tutorial, you’ll build an app whose UI reacts in real-time to the platform’s network state events. Then, you’ll add support to stream and display an image’s bytes from the platform on Flutter.

But first, take a closer look at platform channels.

Platform Channels: An Overview

Like previously mentioned, Platform Channels are a way to send information from Dart to the native iOS or Android code. The platform channels API is made up of a few different utilities:

Binary Messaging is the lowest level Platform channel API. It’s a bi-directional and synchronous API that sits between Dart and platform code and facilitates passing byte buffers over a channel. It’s used to send sequences of bytes between Dart and platforms, such as iOS and Android.

The rest of the platform channel APIs are built atop Binary Messaging. They add support for high-level data structures like strings, maps and lists. As stated earlier, platform channel consists of MessageChannel, MethodChannel and EventChannel.

MessageChannel exposes raw data in the form of byte buffers. You can pass a codec to interpret the bytes to a high-level data structure, like an image. While Dart provides some off-the-shelf codecs, you can also write your own custom ones.

MethodChannel invokes named methods, with or without arguments, between Dart and native code. You can use it to send a one-off send-and-reply message. It doesn’t support a continuous stream of data.

EventChannel exposes data from the platform to Dart as streams. When you subscribe to an event channel on the Dart end, you get a continuous stream of data from iOS or Android.

To recap, Platform Channels allow you to send and received data both to and from platforms across a channel. In this case, the channel is a string, an identifier of the message and its destination.

With that out of the way, it’s time to write some code and start consuming some events!

Setting Up Event Consumption in Dart

Before you can use an EventChannel, you need to declare it.

Open network_stream_widget.dart. Inside NetworkStreamWidget, declare the eventChannel above build() like this:

final _eventChannel = const EventChannel('platform_channel_events/connectivity');

Then import 'package:flutter/services.dart' in the same file.

Congraulations, you’ve just created your first platform channel! You created an EventChannel. The eventChannel‘s name is "platform_channel_events/connectivity". The name can be anything you want, but it has to be unique.

Next, you’ll need a set of constant values for the supported network states. Like the event name, these values can be arbitrary, but must be consistent across Android, iOS, and Dart.

In constants.dart, inside Constants, add:

/// Event for when network state changes to Wifi
static const wifi = 0xFF;

/// Event for when network state changes to cellular/mobile data
static const cellular = 0xEE;

/// Event for when network is disconnected
static const disconnected = 0xDD;

/// Event for when network state is a state you do not
/// support (e.g VPN or Ethernet on Android)
static const unknown = 0xCC;

This code only supports wifi, cellular and disconnected states. You can use unknown for any other network state that is out of the scope of this tutorial.

That set of constants is great, but it’d be great to have an enumerated set of values for better type safety. In utils.dart, add the following declaration right above the commented statements:

/// Connection is an enum of supported network states
enum Connection {
  /// When connection state is [Constants.wifi]
  wifi,

  /// When connection state is [Constants.cellular]
  cellular,

  /// When connection state is [Constants.disconnected]
  disconnected,

  /// When connection state is [Constants.unknown]
  unknown
}

Now that you’ve got an enum, you need a function to map the network constants to Connection. First, import 'constants.dart' in utils.dart. Then add this statement below the enum you just declared:

/// converts the network events to the appropriate values of 
/// the [Connection] enum
Connection intToConnection(int connectionInt) {
  var connection = Connection.unknown;
  switch (connectionInt) {
    case Constants.wifi:
      connection = Connection.wifi;
      break;
    case Constants.cellular:
      connection = Connection.cellular;
      break;
    case Constants.disconnected:
      connection = Connection.disconnected;
      break;
    case Constants.unknown:
      connection = Connection.unknown;
      break;
  }
  return connection;
}

Each connection state is going to be shown with a different color and different text, so you’ll need two more similar functions: getConnectionColor() and getConnectionMessage(). getConnectionColor() maps Connection to Color objects while getConnectionMessage() maps Connection to user-readable strings.

You’ll find these functions in utils.dart. Uncomment them and then import 'package:flutter/material.dart'.

Since you’re using an EventChannel, you need to get the actual stream from the eventChannel object.

Open network_stream_widget.dart. In NetworkStreamWidget, just above the return statement in build(), add:

final networkStream = _eventChannel
    .receiveBroadcastStream()
    .distinct()
    .map((dynamic event) => intToConnection(event as int));

Then import 'utils.dart' into same file.

In the piece of code above, you use the receiveBroadcastStream() method to get a stream of events from the EventChannel. You used the distinct() method to filter out duplicate events. Finally, you converted the events to Connection values with intToConnection().

Run the project. You’ll see a UI similar to the previous screenshot.

Starter project screenshot