Розробка 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
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Розробка 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 очікує int, ви отримуєте TypeError. Явна перевірка типів на нативній стороні є обов'язковою.

Виділення у окремий 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 очищує обробник. Без цього під час гарячої перезавантаження Flutter старий обробник залишається в пам'яті, й наступний виклик через канал срабативає двічі.

Багатопоточність: основна пастка

Kotlin-частина MethodChannel.setMethodCallHandler викликається на головному потоці. Будь-яка блокуюча операція всередину спричиняє 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() має бути викликано на головному потоці—це вимога 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 займає 1–3 дні для простого MethodChannel з 2–4 методами. EventChannel з управлінням життєвим циклом та тестами займає 3–5 днів. Упакування як переповторно використовуваного плагіна додає 1–2 дні. Вартість розраховується індивідуально.