Irbisa · Шпаргалка
Платформенные каналы
MethodChannel, EventChannel, разработка плагинов, FFI
Как работают платформенные каналы (
MethodChannel,EventChannel,BasicMessageChannel) и синхронны ли вызовы?const channel = MethodChannel('com.example.app/battery'); Future<int?> batteryLevel() async { try { return await channel.invokeMethod<int>('getBatteryLevel'); } on PlatformException catch (e) {…Что такое
EventChannelи когда он нужен вместоMethodChannel?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);…Как правильно обрабатывать ошибки через платформенный канал?
class CameraService { static const _channel = MethodChannel('com.example/camera'); Future<String> takePicture() async { try { final path = await _channel…Dart падает с ошибкой типа при касте результата канала к
Map<String, dynamic>, а Kotlin падает при касте таймстемпа кInt. Что происходит?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…Какие типы можно передавать через платформенный канал и как отправить собственный объект?
class Tile { const Tile(this.z, this.x, this.y, this.bytes); final int z, x, y; final Uint8List bytes;}…Что такое Pigeon и зачем он нужен вместо рукописного кода на
MethodChannel?// 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',…Как запросить runtime-разрешения, например камеру или геолокацию, во Flutter-приложении?
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…Вы добавили плагин, запустили приложение, и каждый вызов бросает
MissingPluginException. Как найти причину?Часть вызовов из Dart к этому Android-обработчику никогда не завершается, а другие роняют приложение. Что не так?
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") }}Найдите баг: бейдж входящих перестаёт обновляться, как только открывают чат, а после закрытия чата пуши не получает вообще ни один экран.
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()При каждой перестройке этого экрана нативный лог показывает
onCancel, затемonListen, и показания прерываются. Почему?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'), ); }}Найдите баг: после того как пользователь уходит с экрана пробежки, индикатор геолокации не гаснет и батарея продолжает садиться.
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']}');}EventChannelакселерометра на 200 Гц вызывает подтормаживания UI, а значения отстают тем сильнее, чем дольше открыт экран. Где проблема?Как написать Flutter-плагин с нуля и когда делить его на federated-плагин?
# my_plugin/pubspec.yaml: the app-facing package of a federated pluginname: my_pluginversion: 1.0.0 flutter: plugin:…На каких потоках выполняются вызовы платформенных каналов и как не дать медленному нативному обработчику заморозить 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…Фича применяет C++-фильтр к каждому кадру камеры и показывает системный диалог доступа к фото. Что идёт через
dart:ffi, а что через канал?Нативный код декодирует фото на 12 мегапикселей и возвращает сырые пиксели через
MethodChannel; приложение тормозит и иногда падает по памяти. Что менять?// 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);…Как вызвать платформенный код из фонового изолята?
import 'dart:isolate';import 'package:flutter/services.dart'; Future<String> hashViaPlatform(String path) async { final token = RootIsolateToken.instance!; // read on the root isolate…Дизайнер хочет нативную карту внутри Flutter-экрана. Как работают platform views и чего они стоят?
Widget nativeMap() { switch (defaultTargetPlatform) { case TargetPlatform.android: return const AndroidView( viewType: 'com.example/map', creationParams: {'zoom': 12},…Android-плагину нужна Activity, чтобы запросить разрешение. Как получить её без утечки?
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) {…Как выполнять Dart-код в фоне на Android и iOS (периодическую синхронизацию, background fetch, отслеживание геолокации)?
@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();…Как встроить Flutter в существующее нативное приложение и зачем заранее прогревать и кешировать
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")…При холодном старте по тапу на уведомление теряется payload: нативный код вызывает
invokeMethod, а Dart его не слышит. Почему и что сделать вместо этого?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');…C-библиотека сообщает о прогрессе, вызывая ваш колбэк со своего рабочего потока. Как доставить это в Dart, не уронив процесс?
// 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>>);…