Вы запускаете 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-приложения на реальном устройстве;
- документацию по каждому методу и пример использования;
- передачу исходников, access к репозиторию, инструкцию по сборке.
Мы гарантируем, что канал не упадёт при горячей перезагрузке и не вызовет 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.'
Получите консультацию по вашему проекту. Свяжитесь с нами, чтобы обсудить ваш сценарий — пришлём коммерческое предложение и сроки.







