skip to content

In Flutter, what is a MethodChannel for, and how does a Dart call such as reading the battery-saver state reach host code and come back?

level: juniorimportance: must knowfreq 68%

answer

  1. named pipe, same string both sides
  2. invokeMethod returns a Future
  3. StandardMethodCodec by default
  4. host handler replies success, error or notImplemented
  5. FIFO order, asynchronous only

basics

~20 s

A MethodChannel is a named, asynchronous pipe from Dart to host code: invokeMethod encodes a method name and arguments, the Kotlin or Swift handler registered under the same channel name replies, and Dart awaits the result as a Future.

solid answer

~40 s

A `MethodChannel` lets Dart call platform APIs Flutter does not wrap, such as the OS battery-saver flag. Dart creates `const MethodChannel('app/power')` and calls `invokeMethod<bool>('isPowerSaveMode')`; the call is encoded by `StandardMethodCodec` into bytes and sent through the engine's `BinaryMessenger`. On the host, a handler registered under the same channel name reads `call.method`, does the work and answers with `result.success(value)`, `result.error(code, message, details)` or `result.notImplemented()`. Dart receives a `Future<T?>` that completes with the value or throws `PlatformException` / `MissingPluginException`. Everything is asynchronous, calls on a built-in channel arrive in FIFO order, and the channel is not type safe: both sides must agree on names and argument shapes by convention.

code

dart · 13 lines
dart
import 'package:flutter/services.dart';

const _power = MethodChannel('com.example.app/power');

Future<bool> isBatterySaverOn() async {
  try {
    final bool? on = await _power.invokeMethod<bool>('isPowerSaveMode');
    return on ?? false;
  } on PlatformException catch (e) {
    debugPrint('power query failed: ${e.code} ${e.message}');
    return false;
  }
}

go deeper

for a junior

Recall the pieces: a channel name shared by both sides, invokeMethod returning a Future, and a host handler that replies with success, error or notImplemented.

for a middle

Explain the codec step and why the result is nullable and only cast at runtime, and show how a Dart handler set with setMethodCallHandler answers host-initiated calls.

for a senior

Discuss the lack of type safety, FIFO ordering, the unanswered-reply hang and when to replace hand-written channels with generated bindings or an existing plugin.

for a principal

Frame channel design as an API boundary: naming, versioning of method names and argument maps across app releases, and the cost of keeping two hand-synchronised implementations.

## What problem a MethodChannel solves Flutter draws its own UI, but many device facts live only in the host OS: whether **battery saver** (Android's power-save mode, iOS Low Power Mode) is on, sensor readings, vendor SDKs. A **platform channel** is the message pipe between the Dart code in the Flutter engine and the **host** code of the embedding app (Kotlin/Java on Android, Swift/Objective-C on iOS, C++ or C on desktop). `MethodChannel` is the channel shaped like a remote procedure call: Dart names a method, passes arguments and awaits one reply. ## The round trip, step by step 1. Dart creates the channel with a **name**, conventionally reverse-domain: `const MethodChannel('com.example.app/power')`. The name is the whole identity; a channel with the same name elsewhere interferes with it. 2. Dart calls `invokeMethod<bool>('isPowerSaveMode', args)`. The framework wraps this in a `MethodCall`, and the channel's codec (by default `StandardMethodCodec`, built on `StandardMessageCodec`) encodes it to bytes. 3. The bytes go through the `BinaryMessenger` (by default `ServicesBinding.defaultBinaryMessenger`) into the engine and on to the host. 4. On the host, a handler registered on a channel with the **same name and a compatible codec** decodes the call, switches on the method name and replies with one of three outcomes: **success** with a value, **error** with a code, message and details, or **not implemented**. 5. The reply travels back and completes the Dart `Future<T?>`: a value, a thrown `PlatformException`, or a thrown `MissingPluginException`. ```dart import 'package:flutter/services.dart'; const _power = MethodChannel('com.example.app/power'); Future<bool> isBatterySaverOn() async { final bool? on = await _power.invokeMethod<bool>('isPowerSaveMode'); return on ?? false; } ``` ## Properties worth stating in an interview - **Asynchronous only.** `invokeMethod` returns a `Future`; there is no synchronous channel call, so the Dart UI isolate never blocks waiting for the host. - **FIFO.** The framework documents that calls sent on a built-in `MethodChannel` are received by the platform in the order they were sent. - **Not type safe.** The Dart type argument `T` is only a cast at runtime; if the host returns a different type, a `TypeError` is thrown. Typed, generated wrappers are what Pigeon adds on top. - **Nullable result.** The return type is `Future<T?>`: the host may reply `null` as a legitimate value. - **Bidirectional.** Dart can also *receive* calls: `setMethodCallHandler` registers a Dart handler that the host invokes on the same channel. - **Initialisation.** Setting a Dart handler before `WidgetsFlutterBinding.ensureInitialized()` trips an assert, because the default messenger does not exist yet. ## The three channel classes | Class | Shape | Typical use | |---|---|---| | `MethodChannel` | request, one reply | read the battery-saver flag, start a scan | | `EventChannel` | host-to-Dart stream via `receiveBroadcastStream` | step counts, sensor or connectivity updates | | `BasicMessageChannel<T>` | raw messages with a chosen `MessageCodec<T>` (`send` / `setMessageHandler`) | custom protocols, `StringCodec` or `BinaryCodec` payloads | All three are thin layers over the same `BinaryMessenger`, and all use a codec to turn Dart values into bytes. ## When not to hand-write one - If a maintained plugin already wraps the API, depend on it instead of writing a channel. - If there are many methods with structured arguments, generated code gives compile-time checking on both sides. - If the native side is a C library, `dart:ffi` calls it directly without a message hop. A MethodChannel remains the baseline every Flutter developer should be able to read and write: it is how most plugins are built underneath.

  • Can the host call into Dart over the same MethodChannel?
    Yes. Dart registers `setMethodCallHandler` on the channel, and the host calls `invokeMethod` on its own channel object with the same name. The Dart handler returns a `Future`; a returned value becomes a success reply, a thrown `PlatformException` becomes an error reply, and a thrown `MissingPluginException` becomes the empty 'not implemented' reply.
  • What happens if the host handler never calls result?
    The Dart `Future` from `invokeMethod` simply never completes, because the framework completes it only when a reply arrives. Every branch of the host handler, including the fallback for unknown method names, must answer with `success`, `error` or `notImplemented`.
  • Why does invokeMethod<Map<String, int>> fail at runtime?
    The codec always decodes maps as `Map<Object?, Object?>`, and Dart generics are reified, so casting that object to `Map<String, int>` throws a `TypeError`. Use `invokeMapMethod<String, int>`, which calls `Map.cast`, or cast the entries yourself.

A MethodChannel works like a hotel's room-service phone line: you dial a named extension, say what you want, and hang on for one answer (food, an apology, or 'we do not serve that') while the lobby keeps running.

saying these in an interview costs you the question

  • Says invokeMethod blocks the UI until the host replies
  • Thinks the channel name only needs to match on the Dart side
  • Believes the type argument on invokeMethod is checked at compile time against the host
  • Assumes Flutter wraps native views for every platform feature
  • Forgets the host must reply in every branch, including unknown methods