Irbisa · Cheatsheet
Platform Channels
MethodChannel, EventChannel, plugin development, FFI
How do platform channels work (
MethodChannel,EventChannel,BasicMessageChannel), and are the calls synchronous?const channel = MethodChannel('com.example.app/battery'); Future<int?> batteryLevel() async { try { return await channel.invokeMethod<int>('getBatteryLevel'); } on PlatformException catch (e) {…What is
EventChanneland when do you use it instead ofMethodChannel?class BatteryState { static const _channel = EventChannel('com.example/battery_state'); // created once: every listener shares one native onListen static final Stream<int> percent = _channel.receiveBroadcastStream().map((e) => e as int);…How should you handle errors across a platform channel?
class CameraService { static const _channel = MethodChannel('com.example/camera'); Future<String> takePicture() async { try { final path = await _channel…Dart throws a type error casting a channel result to
Map<String, dynamic>, and Kotlin crashes casting a timestamp toInt. What is going on?const channel = MethodChannel('com.example/profile'); // wrong: _TypeError, the platform returned Map<Object?, Object?>final bad = await channel.invokeMethod('load') as Map<String, dynamic>; // right: the typed helpers cast for you…Which types can cross a platform channel, and how do you send your own objects?
class Tile { const Tile(this.z, this.x, this.y, this.bytes); final int z, x, y; final Uint8List bytes;}…What is Pigeon and why use it over hand-written
MethodChannelcode?// pigeons/device.dart: the single source of truthimport 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/device.g.dart', kotlinOut: 'android/src/main/kotlin/com/example/Device.g.kt',…How do you request runtime permissions such as camera or location in a Flutter app?
Future<bool> ensureCameraPermission() async { final status = await Permission.camera.request(); // returns at once if granted if (status.isGranted || status.isLimited) return true; if (status.isPermanentlyDenied) { // the system dialog will not appear again: explain, then send to Settings…You add a plugin, run the app, and every call throws
MissingPluginException. How do you work out why?Some Dart calls to this Android handler never complete, and others crash the app. What is wrong?
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "readFile" -> scope.launch(Dispatchers.IO) { val path = call.argument<String>("path")!! result.success(File(path).readText()) } "clearCache" -> { cacheDir.deleteRecursively() result.success(null) result.success(true) } "ping" -> if (connected) result.success("pong") }}Find the bug: the inbox badge stops updating as soon as a chat is opened, and after the chat is closed no screen receives pushes at all.
class PushListener { PushListener(this.onMessage) { _channel.setMethodCallHandler((call) async { if (call.method == 'onMessage') onMessage(call.arguments as String); }); } static const _channel = MethodChannel('com.example/push'); final void Function(String text) onMessage; void dispose() => _channel.setMethodCallHandler(null);} // InboxBadge and ChatScreen each create one in initState// and call dispose() in their own dispose()Each time this screen rebuilds, the native log shows
onCancelthenonListen, and readings pause. Why?class HeartRate extends StatelessWidget { const HeartRate({super.key, required this.unit}); final String unit; static const _channel = EventChannel('com.example/heart_rate'); @override Widget build(BuildContext context) { return StreamBuilder<int>( stream: _channel.receiveBroadcastStream().cast<int>(), builder: (context, snap) => Text('${snap.data ?? '--'} $unit'), ); }}Find the bug: after the user leaves the run screen, the location indicator stays on and the battery keeps draining.
class _RunMapState extends State<RunMap> { static const _location = EventChannel('com.example/location'); late final StreamSubscription<Object?> _sub; Map<Object?, Object?>? _last; @override void initState() { super.initState(); _sub = _location.receiveBroadcastStream().listen((event) { setState(() => _last = event as Map<Object?, Object?>); }); } @override Widget build(BuildContext context) => Text('${_last?['lat']}, ${_last?['lng']}');}An accelerometer
EventChannelat 200 Hz makes the UI hitch, and the values lag further behind the longer the screen is open. Where is the problem?How do you build a Flutter plugin from scratch, and when do you split it into a federated plugin?
# my_plugin/pubspec.yaml: the app-facing package of a federated pluginname: my_pluginversion: 1.0.0 flutter: plugin:…Which threads do platform channel calls run on, and how do you keep a slow native handler from freezing the UI?
class HashPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { private var channel: MethodChannel? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val messenger = binding.binaryMessenger // calls arrive on a background thread, in order, instead of the main thread…A feature runs a C++ filter on every camera frame and also shows the system photo permission dialog. What goes over
dart:ffiand what over a channel?Native decodes a 12-megapixel photo and returns raw pixels over a
MethodChannel; the app stutters and sometimes runs out of memory. What do you change?// wrong: pixels over the channel, copied on both sidesfinal pixels = await channel.invokeMethod<Uint8List>('decodePhoto', path);ui.decodeImageFromPixels(pixels!, 4000, 3000, ui.PixelFormat.rgba8888, (image) { /* ... */ }); // ~48 MB in flight // right: keep the bytes native and hand Dart a path to an encoded filefinal jpegPath = await channel.invokeMethod<String>('exportJpeg', path);…How do you call platform code from a background isolate?
import 'dart:isolate';import 'package:flutter/services.dart'; Future<String> hashViaPlatform(String path) async { final token = RootIsolateToken.instance!; // read on the root isolate…A designer wants a native map inside a Flutter screen. How do platform views work, and what do they cost?
Widget nativeMap() { switch (defaultTargetPlatform) { case TargetPlatform.android: return const AndroidView( viewType: 'com.example/map', creationParams: {'zoom': 12},…Your Android plugin needs the Activity to request a permission. How do you get it without leaking it?
class PermissionPlugin : FlutterPlugin, ActivityAware, MethodCallHandler, PluginRegistry.RequestPermissionsResultListener { private var binding: ActivityPluginBinding? = null // only between attach and detach private var pending: MethodChannel.Result? = null // onAttachedToEngine / onDetachedFromEngine: create and clear the MethodChannel override fun onAttachedToActivity(b: ActivityPluginBinding) {…How do you run Dart code in the background on Android and iOS (a periodic sync, a background fetch, location tracking)?
@pragma('vm:entry-point')void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { DartPluginRegistrant.ensureInitialized(); // a fresh isolate: nothing from the UI isolate exists here final db = await AppDatabase.open();…How do you add Flutter to an existing native app, and why do you pre-warm and cache a
FlutterEngine?class App : Application() { override fun onCreate() { super.onCreate() // start once, early: the first Flutter screen then opens without a blank delay val engine = FlutterEngine(this) engine.navigationChannel.setInitialRoute("/checkout")…A cold start from a notification tap loses the payload: native calls
invokeMethodand Dart never hears it. Why, and what do you ship instead?class Push { static const _channel = MethodChannel('com.example/push'); // pull: survives the cold start because native kept the payload static Future<PushPayload?> initial() async { final map = await _channel.invokeMapMethod<String, Object?>('getInitialMessage');…A C library reports progress by calling your callback from its own worker thread. How do you deliver that into Dart without crashing the process?
// C: typedef void (*progress_cb)(int32_t percent);// void start_job(const char* path, progress_cb cb); // cb runs on a worker threadtypedef _ProgressC = Void Function(Int32);typedef _StartJobC = Void Function(Pointer<Utf8>, Pointer<NativeFunction<_ProgressC>>);typedef _StartJob = void Function(Pointer<Utf8>, Pointer<NativeFunction<_ProgressC>>);…