Skip to main content

Command Palette

Search for a command to run...

Flutter Platform Channels Explained: MethodChannel, EventChannel and BasicMessageChannel

Updated
4 min read
Flutter Platform Channels Explained: MethodChannel, EventChannel and BasicMessageChannel

Flutter handles UI beautifully. Widgets compose, animations run smooth, and the same codebase works across Android and iOS without much friction. But there's a gap — Flutter's Dart layer has no direct access to platform APIs. It can't read the battery level, stream sensor data, or call a native SDK without some help.

Platform Channels are that help. They're Flutter's built-in bridge for passing messages between Dart and native code. There are three of them, each suited to a different communication pattern.


What Is a Platform Channel?

Think of Flutter and native code as two teams working in separate rooms. They can't walk into each other's spaces directly, but they can pass notes through a slot in the wall. Platform Channels are that slot.

Every channel has a name — a string identifier shared by both sides so they can find each other. Beyond that, the three channel types differ in how the conversation flows.


MethodChannel: Ask Once, Get an Answer

MethodChannel is the most common type. Flutter sends a named method call, native code handles it and replies, and that's the end of the exchange.

It's the right tool any time you want to ask native code to do something and get a result back — check the battery, read a device ID, trigger a native dialog.

Flutter (Dart):

const platform = MethodChannel('samples.flutter.dev/battery');

Future<void> getBatteryLevel() async {
  try {
    final int result = await platform.invokeMethod('getBatteryLevel');
    print('Battery is $result%.');
  } catch (e) {
    print('Error: $e');
  }
}

Android (Kotlin):

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/battery")
  .setMethodCallHandler { call, result ->
    if (call.method == "getBatteryLevel") {
      val batteryLevel = getBatteryLevel()
      result.success(batteryLevel)
    } else {
      result.notImplemented()
    }
  }

Both sides reference the same channel name. Flutter calls invokeMethod, native handles it, and calls result.success() with the response. One request, one response, done.


BasicMessageChannel: Two-Way Messaging Without a Command Structure

BasicMessageChannel is less formal. Either side can send a message at any point, and the other side can reply — or not. There's no method name, no command-response pairing. It's just a communication channel between two parties.

This suits situations where you want ongoing, loosely structured communication without the overhead of named method calls.

Flutter (Dart):

const channel = BasicMessageChannel<String>(
  'samples.flutter.dev/message',
  StringCodec(),
);

void sendMessage() {
  channel.send('Hello from Flutter!');
}

void receiveMessages() {
  channel.setMessageHandler((message) async {
    print('From Native: $message');
    return 'Received.';
  });
}

Android (Kotlin):

channel = BasicMessageChannel(
  flutterEngine.dartExecutor.binaryMessenger,
  "samples.flutter.dev/message",
  StringCodec.INSTANCE
)

channel.setMessageHandler { message, reply ->
  println("Flutter says: $message")
  reply.reply("Got it.")
}

Both sides set a message handler and can send messages independently. The codec (StringCodec here) defines how messages are serialized — you can also use StandardMessageCodec for more complex data types.


EventChannel: Continuous Data from Native to Flutter

EventChannel is built for streaming. Native code produces a continuous flow of data — sensor readings, location updates, audio levels — and Flutter subscribes to that stream. The data flows until either side cancels.

Flutter (Dart):

const eventChannel = EventChannel('samples.flutter.dev/sensor');

void listenToSensor() {
  eventChannel.receiveBroadcastStream().listen(
    (event) {
      print('Sensor update: $event');
    },
    onError: (error) {
      print('Error: $error');
    },
  );
}

Android (Kotlin):

EventChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/sensor")
  .setStreamHandler(object : EventChannel.StreamHandler {
    override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
      startSendingSensorData(events)
    }

    override fun onCancel(arguments: Any?) {
      stopSensorUpdates()
    }
  })

When Flutter subscribes, onListen fires on the native side — that's when you start pushing data to the EventSink. When Flutter unsubscribes or the stream is cancelled, onCancel fires and you can clean up.

This is the right choice for any native data source that produces updates over time: accelerometer, GPS, microphone input, network connectivity changes.


Choosing the Right Channel

ChannelCommunication PatternBest For
MethodChannelRequest → ResponseOne-off commands: battery level, device info, native dialogs
BasicMessageChannelFree-form bi-directionalLoosely structured ongoing communication
EventChannelNative → Flutter streamContinuous data: sensors, location, audio

A Few Practical Notes

Channel names are just strings, but treat them seriously. Use a reverse-domain format like com.yourapp.feature/channel to avoid collisions, especially if you're using third-party plugins that also register channels.

Error handling matters on both sides. On the Dart side, invokeMethod throws a PlatformException if native code calls result.error(). Catch it explicitly rather than letting it bubble up silently.

Threading on Android. The MethodCallHandler runs on the main thread by default. If your native operation is blocking or IO-heavy, dispatch it to a background thread and call result.success() from there when you're done.

iOS works the same way. The channel names and Dart code are identical; only the native implementation differs (Swift or Objective-C instead of Kotlin/Java). Structurally, the pattern is the same on both platforms.


In Summary

Platform Channels are how Flutter reaches into the native platform when it needs to. MethodChannel handles one-off requests. BasicMessageChannel handles free-form conversation. EventChannel handles data streams.

Most Flutter apps only need MethodChannel for occasional platform calls and EventChannel for anything that streams. If you've been avoiding native integration because it seemed complex, the actual API surface is smaller than it looks — it's just message passing with a name attached.

More from this blog

A

Anmol's blog

17 posts