In Flutter, what problem does the pigeon package solve compared with a hand-written MethodChannel that matches method names as strings?
answer
- one Dart file describes the API
- strings and casts become generated code
- typed Dart class plus host interface
- same codec, both sides, one run
basics
~20 sPigeon generates both ends of a platform channel from one Dart definition file, so channel names, argument order and types are produced by a tool instead of matched by hand, and a mismatch becomes a compile error instead of a runtime failure.
solid answer
~40 sWith a hand-written `MethodChannel`, Dart and host code agree on method names, argument maps and types only by convention: a typo is a `MissingPluginException`, a wrong type a runtime cast failure. Pigeon, a dev dependency, reads a Dart definition file with `@HostApi()` or `@FlutterApi()` abstract classes and data classes, and generates a typed Dart class, a host interface or protocol (Kotlin, Java, Swift, Objective-C, C++ or GObject) with a `setUp` function, data classes and enums on both sides, and a shared codec. Your Kotlin or Swift code just implements the interface, and the Dart side calls ordinary typed methods returning `Future`s. The calls still travel over platform channels, so the asynchronous model and threading rules are unchanged; what disappears is the stringly-typed glue.
code
dart · 32 linesimport 'package:pigeon/pigeon.dart';
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/scanner_api.g.dart',
dartPackageName: 'shop_app',
kotlinOut: 'android/app/src/main/kotlin/com/example/shop/ScannerApi.g.kt',
kotlinOptions: KotlinOptions(),
swiftOut: 'ios/Runner/ScannerApi.g.swift',
swiftOptions: SwiftOptions(),
))
enum BarcodeFormat { qr, ean13, code128 }
class ScanRequest {
ScanRequest({required this.format, required this.useTorch});
BarcodeFormat format;
bool useTorch;
}
class ScanResult {
ScanResult({required this.rawValue, required this.format});
String rawValue;
BarcodeFormat format;
}
@HostApi()
abstract class ScannerHostApi {
bool isSupported();
@async
ScanResult? scanOnce(ScanRequest request);
}go deeper
Recall that Pigeon generates both sides of a platform channel from one Dart file, so names and types no longer have to be matched by hand.
Explain what the definition file contains, what each generated file provides, and that calls remain asynchronous channel messages.
Argue when the codegen step pays for itself and how regeneration fits into the build and review process.
Decide team-wide whether native bridges use Pigeon by default, weighing typed contracts against tool churn across Pigeon releases.
## The problem with hand-written channels A **platform channel** carries messages between Dart and the **host** (the Kotlin, Swift or C++ side of the app). Written by hand, a `MethodChannel` call relies on three informal contracts: - the **channel name** and **method name** are identical strings on both sides; - the **arguments** are packed into a map or list that both sides read with the same keys and order; - the **result type** the host sends is what the Dart code casts to. Nothing checks these at build time. A renamed method, a changed key, or an `int` read as the wrong width is found by a user, as an exception or a crash. ## What Pigeon does instead **Pigeon** is a code generator published as `package:pigeon`, added as a `dev_dependency`. You describe the interface once, in a Dart file kept outside `lib/`, using only declarations: 1. **Data classes** with fields of supported types, plus **enums**; classes can nest. 2. An **`@HostApi()`** abstract class for methods implemented on the host and called from Dart. 3. Optionally an **`@FlutterApi()`** class for methods implemented in Dart and called from the host, and an **`@EventChannelApi()`** class for streams. Running `dart run pigeon --input pigeons/scanner_api.dart` then writes: | Output | What it contains | |---|---| | Dart file | a class with typed methods returning `Future`s, the data classes with encode/decode, a codec | | Kotlin or Java | an interface to implement, a `setUp(binaryMessenger, api)` companion function, data classes | | Swift or Objective-C | a protocol to conform to, a setup class, structs or classes for the data | | C++ / GObject | an abstract class or vtable for Windows and Linux | ## What stays the same - Messages still go through a `BinaryMessenger` on channels (the generated names look like `dev.flutter.pigeon.<package>.<Api>.<method>`), so every call is **asynchronous from Dart**. - Host handlers still run on the platform main thread unless you choose a background task queue. - The codec is still built on `StandardMessageCodec`, extended with type bytes for your data classes and enums. ## What you gain - **Compile-time checking on both sides.** Change a field in the definition, regenerate, and the Kotlin and Swift implementations stop compiling until they are updated. - **Real data classes** instead of `Map<Object?, Object?>` casts. - **Consistent error mapping.** A host `FlutterError` (Kotlin) or `PigeonError` (Swift) surfaces in Dart as `PlatformException`. - **Fewer handwritten switch statements** routing method names to implementations. ## What it costs - A code-generation step that must be rerun after every definition change, and generated files to keep in version control or regenerate in CI. - Generated code changes shape between Pigeon releases; the README says both sides must be generated with the **same version** and that generated code should not appear in a package's public API. - One more tool for the team to learn, which is not worth it for a single method returning a `bool`. For a bridge to a native barcode-scanning SDK with request and result objects, several methods and callbacks, those costs are usually small next to removing a whole class of runtime-only bugs.
- Does Pigeon make channel calls synchronous or remove the channel hop?No. In its default mode the generated code still sends messages over platform channels, so every call returns a `Future` in Dart and pays for encoding and a message hop. Pigeon 29 adds an experimental native-interop mode over FFI and JNI with synchronous calls, but the standard mode is typed channels.
- Why is the definition file kept outside lib/?It imports `package:pigeon`, a dev dependency, and is input to a generator, not app code. Keeping it in a folder such as `pigeons/` stops it being compiled into the app, while the generated Dart file goes under `lib/` where the app imports it.
saying these in an interview costs you the question
- Says Pigeon replaces platform channels with a synchronous native call
- Thinks Pigeon needs no host code at all
- Believes the Dart definition file ships inside the app
- Edits generated files by hand instead of the definition