Разработка Platform Channel для Flutter-приложения (Android)

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Разработка Platform Channel для Flutter-приложения (Android)
Сложная
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Разработка Platform Channel для Flutter-приложения (Android)

Platform Channel — мост между Dart-кодом Flutter и нативным Android-кодом (Kotlin/Java). Нужен тогда, когда pub.dev не предлагает готового плагина: специфичный SDK от производителя оборудования, низкоуровневая работа с аудио через AudioRecord, интеграция с корпоративными MDM-системами. Три типа каналов: MethodChannel (RPC), EventChannel (поток событий из native в Dart), BasicMessageChannel (двусторонняя передача произвольных сообщений).

MethodChannel: вызов нативного метода из Dart

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.

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
    }
}

Регистрация в MainActivity:

EventChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/accelerometer")
    .setStreamHandler(SensorStreamHandler(this))

Dart:

class AccelerometerService {
  static const _channel = EventChannel('com.example.app/accelerometer');

  Stream<Map<String, dynamic>> get accelerometerStream {
    return _channel.receiveBroadcastStream().map(
      (event) => Map<String, dynamic>.from(event as Map),
    );
  }
}

// Использование:
AccelerometerService().accelerometerStream.listen((data) {
  setState(() {
    x = data['x'] as double;
    y = data['y'] as double;
  });
});

Типы данных: что проходит через канал

Platform Channel автоматически конвертирует между Dart и Kotlin следующие типы:

Dart Kotlin
null null
bool Boolean
int Int / Long
double Double
String String
Uint8List ByteArray
List List<Any?>
Map HashMap<Any, Any?>

Важно: int в Dart конвертируется в Int если помещается, иначе Long. Если нативная сторона возвращает Int, а Dart ожидает int — проблем нет. Но если Kotlin вернул Double, а Dart ожидает intTypeError в Dart. Явная проверка типов на нативной стороне обязательна.

Выделение в отдельный Plugin

Для переиспользования Platform Channel в нескольких проектах оформляют как Flutter Plugin:

flutter create --template=plugin --platforms=android my_nfc_plugin

Точка входа — класс, реализующий FlutterPlugin:

class MyNfcPlugin : FlutterPlugin, MethodCallHandler {
    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(binding.binaryMessenger, "my_nfc_plugin")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        // обработка
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

onDetachedFromEngine — очистка handler. Без неё при горячей перезагрузке Flutter старый handler остаётся в памяти, и следующий вызов через канал срабатывает дважды.

Многопоточность: главная ловушка

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 { } обязательны для ответов из фоновых потоков.

Вызов result.success() дважды — крэш Flutter engine: Methods can only be called once. Если операция отменена — вызывать result.error() или не вызывать ничего (но тогда Dart-сторона будет ждать вечно). Лучшая практика: явно возвращать ошибку в случае отмены.

Тестирование

Юнит-тестирование логики на Kotlin — без Flutter:

@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) }
}

Интеграционные тесты через integration_test пакет Flutter — запускают реальное Flutter приложение с нативной частью на эмуляторе.

Разработка Platform Channel: простой MethodChannel с 2-4 методами — 1-3 дня. EventChannel с управлением жизненным циклом и тестами — 3-5 дней. Оформление как переиспользуемого plugin — плюс 1-2 дня. Стоимость рассчитывается индивидуально.