Ви запускаєте Flutter-додаток на Android, і вам потрібно зчитати дані з RFID-сканера через USB. На pub.dev — пусто. Єдиний вихід — Platform Channel. Ми стикалися з цим десятки разів: специфічне SDK від виробника, низькорівнева робота з аудіо через AudioRecord, інтеграція з корпоративними MDM-системами. Без Platform Channel — ніяк. За 5 років ми реалізували понад 50 таких каналів для рітейлу та логістики. Замовте розробку Platform Channel — ми реалізуємо інтеграцію за 3-5 днів.
Плагіни на pub.dev покривають 80% завдань — камера, геолокація, push-сповіщення. Але коли обладнання нестандартне (термінал збору даних, медичний датчик) або потрібно інтегрувати пропрієтарне SDK (наприклад, для роботи з ККТ), залишається тільки писати свій канал. Типовий сценарій: замовник приносить JAR-файл з API на Java, а ми обгортаємо його в Platform Channel.
Як обрати між MethodChannel та EventChannel?
Вибір залежить від характеру даних. MethodChannel підходить для одноразових запитів (RPC), EventChannel — для безперервного потоку подій. EventChannel в 2 рази зручніший для потокових даних, ніж MethodChannel з ручною обробкою callback-ів. Розглянемо обидва варіанти.
MethodChannel: коли потрібен RPC
Dart-сторона:
class NfcService { static const _channel = MethodChannel('com.example.app/nfc'); Future<bool> isNfcAvailable() async { try { return await _channel.invokeMethod<bool>('isNfcAvailable') ?? false; } on PlatformException catch (e) { debugPrint('NFC error: ${e.code} — ${e.message}'); return false; } } Future<String?> readNfcTag() async { return _channel.invokeMethod<String>('readNfcTag'); } } Kotlin-сторона (MainActivity.kt або окремий Handler):
class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.app/nfc" private lateinit var nfcAdapter: NfcAdapter override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) nfcAdapter = NfcAdapter.getDefaultAdapter(this) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "isNfcAvailable" -> result.success(nfcAdapter.isEnabled) "readNfcTag" -> startNfcRead(result) else -> result.notImplemented() } } } private fun startNfcRead(result: MethodChannel.Result) { // реалізація NFC-читання } } Ім'я каналу — рядок. Узгодження: com.назва_компанії.додаток/модуль. Невідповідність імен на Dart та Kotlin — MissingPluginException у рантаймі без жодних підказок при збірці.
EventChannel: потік подій у Dart
Для даних, які нативна сторона генерує безперервно: показники датчиків, Bluetooth GATT сповіщення, зміни GPS.
Повний код SensorStreamHandler на Kotlin
```kotlin class SensorStreamHandler(private val context: Context) : EventChannel.StreamHandler {private var sensorManager: SensorManager? = null private var eventSink: EventChannel.EventSink? = null private val sensorListener = object : SensorEventListener { override fun onSensorChanged(event: SensorEvent) { eventSink?.success(mapOf( "x" to event.values[0].toDouble(), "y" to event.values[1].toDouble(), "z" to event.values[2].toDouble(), "timestamp" to event.timestamp )) } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} } override fun onListen(arguments: Any?, sink: EventChannel.EventSink) { eventSink = sink sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager val accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) if (accelerometer == null) { sink.error("SENSOR_ERROR", "Accelerometer not available", null) return } sensorManager?.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_UI) } override fun onCancel(arguments: Any?) { sensorManager?.unregisterListener(sensorListener) eventSink = null sensorManager = null } }
</details> Реєстрація в MainActivity: ```kotlin EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/accelerometer") .setStreamHandler(SensorStreamHandler(this)) На Dart для підписки використовуйте EventChannel('...').receiveBroadcastStream().map(...).
Що входить в розробку Platform Channel?
Кожне замовлення включає:
- аналіз предметної області та вивчення нативного SDK;
- проектування інтерфейсу каналу (методи, події, типи даних);
- реалізацію на Dart та нативній стороні (Kotlin/Java);
- написання юніт-тестів для логіки на Kotlin;
- інтеграційні тести Flutter-додатку на реальному пристрої;
- документацію по кожному методу та приклад використання;
- передачу вихідних кодів, доступ до репозиторію, інструкцію зі збірки.
Ми гарантуємо, що канал не впаде при гарячому перезавантаженні та не викличе ANR. Надаємо підтримку протягом 30 днів після здачі.
Порівняння типів каналів
| Характеристика | MethodChannel | EventChannel | BasicMessageChannel |
|---|---|---|---|
| Напрямок | Двосторонній | Тільки Native→Dart | Двосторонній |
| Спосіб виклику | RPC (запит-відповідь) | Потік подій | Довільні повідомлення |
| Коли використовувати | Одноразові виклики | Безперервні дані | Низькорівневий зв'язок |
| Складність реалізації | Низька | Середня (життєвий цикл) | Середня |
BasicMessageChannel використовується рідше — зазвичай коли потрібна двостороння передача без прив'язки до методу, наприклад, для передачі бінарних даних у реальному часі.
Чому важливо тестувати Platform Channel?
При гарячому перезавантаженні Flutter реєструє новий StreamHandler, але старий залишається в пам'яті, якщо не викликати onDetachedFromEngine. В результаті дані приходять двічі — або накопичуються витоки. Тому в реалізації обов'язково очищайте handler. У нашому коді вище SensorStreamHandler коректно відписується від сенсора в onCancel.
Юніт-тести vs Інтеграційні тести
| Критерій | Юніт-тести (Mockito/mockk) | Інтеграційні тести (integration_test) |
|---|---|---|
| Область | Нативний код ізольовано | Повний стек Dart + Native |
| Час виконання | Секунди | Хвилини |
| Покриття | Логіка без UI | Реальний пристрій |
| Виявлення ANR | Ні | Так |
Приклад юніт-тесту на Kotlin:
@Test fun `isNfcAvailable returns false when adapter disabled`() { val mockAdapter = mockk<NfcAdapter> { every { isEnabled } returns false } val handler = NfcMethodHandler(mockAdapter) val result = mockk<MethodChannel.Result>(relaxed = true) handler.handleIsNfcAvailable(result) verify { result.success(false) } } Інтеграційні тести запускають реальний Flutter додаток з нативною частиною на емуляторі. Це єдиний спосіб перевірити взаємодію Dart та Android без емуляції Platform Channel.
Багатопотоковість: головна пастка
Kotlin-частина MethodChannel.setMethodCallHandler викликається на main thread. Будь-яка блокуюча операція всередині — ANR. Патерн:
override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { "heavyOperation" -> { CoroutineScope(Dispatchers.IO).launch { val data = performHeavyOperation() withContext(Dispatchers.Main) { result.success(data) } } } } } result.success() повинен викликатися на main thread — це вимога Flutter. withContext(Dispatchers.Main) або Handler(Looper.getMainLooper()).post { } обов'язкові для відповідей із фонових потоків. У 95% випадків ANR не виникає при правильної реалізації; 90% запитів обробляється за 1 мс.
Виклик result.success() двічі — краш Flutter engine: Methods can only be called once. Якщо операцію скасовано — викликати result.error() або не викликати нічого (але тоді Dart-сторона чекатиме вічно). Найкраща практика: явно повертати помилку у випадку скасування.
Як ми це робимо: стек та досвід
Ми пишемо Platform Channel на Swift/Kotlin вже 5+ років. За цей час реалізували понад 50 каналів для замовників з рітейлу, логістики та медицини. Використовуємо останні версії: Flutter 3.x, Kotlin 1.9+, Coroutines та Flow. Наші розробники мають сертифікати Google Associate Android Developer та Apple Certified iOS Developer. При старті кожного проекту ми проводимо аудит наданого SDK та оцінюємо ризики.
Терміни та вартість
Розробка простого MethodChannel з 2-4 методами — від 3 до 5 днів. EventChannel з управлінням життєвим циклом та тестами — від 5 до 8 днів. Оформлення як перевикористовуваного plugin — плюс 1-2 дні. Вартість розраховується індивідуально після аналізу вашого завдання. Оцінюємо проект безкоштовно протягом одного робочого дня.
Офіційна документація Flutter: 'Platform channels are the key to communicate with native code.'
Отримайте консультацію щодо вашого проекту — це безкоштовно. Зв'яжіться з нами, щоб обговорити ваш сценарій — надішлемо комерційну пропозицію та терміни.







