Створення Flutter-плагіна для нативної функціональності
Ви інтегруєте пропрієтарний SDK партнера — C++ бібліотеку з нативними викликами. Готових плагінів на pub.dev немає, а обгортка через ffi не підходить через складний lifecycle — наприклад, необхідно правильно керувати пам'яттю та синхронізацією з Dart-ізолятом. Знайома ситуація? Тоді пишемо власний плагін через Platform Channels. Ми, команда мобільних розробників з 10+ річним стажем, беремо такі задачі під ключ: від проектування API до публікації на pub.dev та інтеграції у ваш проект. У нас за плечима реалізація понад 40 кастомних плагінів для замовників з фінтеху, медицини та IoT. Знижуємо бюджет на інтеграцію до 40% за рахунок використання Pigeon та оптимізації платформенних каналів.
Як вибрати тип Platform Channel?
Визначення правильного каналу — основа стабільної роботи плагіна. Порівняємо три варіанти:
| Канал | Призначення | Типовий сценарій |
|---|---|---|
MethodChannel |
Виклик методу та отримання результату (request/response) | Отримання версії ОС, читання файлу |
EventChannel |
Потік подій з нативного коду в Dart | Стрім показників сенсора, підписка на BLE-сповіщення |
BasicMessageChannel |
Двонаправлена передача довільних даних з кастомним кодеком | Обмін складними структурами, що не вміщуються в StandardMessageCodec |
Типовий приклад: плагін для роботи з BLE-пристроєм. Сканування пристроїв — EventChannel (безперервний потік знайдених пристроїв). Підключення/відключення — MethodChannel. Отримання сповіщень від характеристики — знову EventChannel.
Чому Pigeon кращий за ручну серіалізацію?
StandardMessageCodec (дефолтний) підтримує примітиви, List, Map. Для кастомних об'єктів — серіалізуємо в Map<String, dynamic> на Dart-стороні та отримуємо HashMap на Kotlin / [String: Any] на Swift. Але це загрожує описками в іменах ключів. Альтернатива — Pigeon: інструмент від Flutter team для генерації type-safe API по .dart-специфікації. Pigeon генерує Kotlin/Swift код з типізованими класами — виключає runtime-помилки від описок в іменах методів. Застосування Pigeon скорочує час розробки на 30–40% та спрощує підтримку. Згідно з офіційною документацією Flutter, використання Pigeon рекомендоване для плагінів зі складною серіалізацією. Детальніше див. Platform Channels.
Порівняйте дві стратегії:
| Підхід | Типобезпека | Час розробки | Підтримка |
|---|---|---|---|
| Ручна серіалізація | Ні (runtime-помилки) | Більше (потрібні тести) | Складніше (узгодження ключів) |
| Pigeon | Так (compile-time) | На 30–40% швидше | Простіше (автогенерація) |
Як ми розробляємо плагін: етапи
- Аналіз вимог: які нативні функції потрібні, які дозволи, lifecycle. Оцінюємо обсяг: 1–2 методи під одну платформу або 10+ з EventChannel для обох.
- Проектування Dart API: контракт через Pigeon або ручні MethodChannel/EventChannel. Визначаємо типи даних.
- Реалізація на iOS (Swift) та Android (Kotlin): використовуємо
FlutterPlugin,ActivityAware,MethodCallHandler. Обробка всіх edge cases та помилок. - Тестування: модульні тести на Dart, інтеграційні тести на обох платформах, тестування на реальних пристроях.
- Інтеграція у ваш проект: підключаємо через git dependency або публікуємо на pub.dev.
- Документація: README з API, прикладами, CHANGELOG.
Типові помилки при роботі з Platform Channels
- Подвійний виклик `result.success()` на Android — IllegalStateException: Reply already submitted. Гарантуємо, що кожен виклик завершується рівно один раз. - Витік `EventSink` при повороті екрана на Android. Рішення: обнуляємо `sink` в `onCancel()` та перевіряємо перед кожним викликом. - Невідповідність імен методів між Dart та нативним кодом. Pigeon виключає цю проблему.Структура плагіна
Створюємо через flutter create --template=plugin my_plugin. Структура:
my_plugin/
lib/my_plugin.dart — Dart API
android/src/.../MyPlugin.kt — Android реалізація
ios/Classes/MyPlugin.swift — iOS реалізація
example/ — приклад додатку для тестуванняDart-сторона оголошує контракт:
class MyPlugin {
static const MethodChannel _channel = MethodChannel('my_plugin');
static Future<String?> getPlatformVersion() async {
return await _channel.invokeMethod<String>('getPlatformVersion');
}
static Stream<ScanResult> get scanResults {
return const EventChannel('my_plugin/scan_results')
.receiveBroadcastStream()
.map((data) => ScanResult.fromMap(Map<String, dynamic>.from(data)));
}
} Android-реалізація: FlutterPlugin + ActivityAware
На Android плагін реалізує FlutterPlugin для lifecycle, MethodCallHandler для обробки викликів. Якщо потрібен Activity (наприклад для permission request), додатково ActivityAware:
class MyPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
private lateinit var channel: MethodChannel
private var activity: Activity? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "my_plugin")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}")
else -> result.notImplemented()
}
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
}Критична деталь: result.success(), result.error() та result.notImplemented() повинні викликатися рівно один раз. Виклик result.success() двічі — краш IllegalStateException: Reply already submitted. Ми гарантуємо відсутність таких помилок у нашому коді.
iOS-реалізація на Swift
public class MyPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "my_plugin",
binaryMessenger: registrar.messenger()
)
let instance = MyPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
} EventChannel та memory leaks
При використанні EventChannel на Android нативний бік отримує EventSink. Типовий витік: тримаємо EventSink в полі класу, Activity перестворюється при повороті екрана, старий EventSink не валідується — і виклик sink.success() після знищення кидає виняток. Рішення: обнуляти sink в onCancel() та перевіряти перед кожним викликом.
Публікація та версіонування
Для внутрішнього використання плагін живе в git-репозиторії та підключається через path або git dependency в pubspec.yaml. Для публікації на pub.dev — flutter pub publish з обов'язковим pubspec.yaml з homepage, repository, повним CHANGELOG.md.
Що входить в роботу
- Розробка Dart API та нативної реалізації для iOS та Android
- Інтеграція з вашим проектом (приклад використання)
- Документація по API та збірці
- Публікація на pub.dev або надання доступу до git-репозиторію
- Гарантія стабільності платформенних каналів та обробка помилок
Розробка плагіна: простий (1–2 методи, одна платформа) — 2–4 дні. Повноцінний cross-platform плагін з EventChannel, permissions та edge-case handling — 2–4 тижні. Вартість розраховується індивідуально. Отримайте консультацію: зв'яжіться з нами для оцінки вашого проекту. Ми вже реалізували понад 40 кастомних плагінів для замовників з фінтеху, медицини та IoT — приєднуйтесь. Замовте розробку плагіна прямо зараз.







