Створення Flutter-плагінів для нативної функціональності
Flutter надає великий набір пакетів на pub.dev, але рано чи пізно зустрічаєтесь з функціональністю, якої немає в жодному готовому плагіні: проприєтарний SDK партнера, специфічна робота з залізом, інтеграція з корпоративною системою через нативний агент. Тут починається розробка власного плагіна через Platform Channels.
Архітектура Platform Channel
Flutter спілкується з нативним кодом через MethodChannel, EventChannel та BasicMessageChannel. Вибір залежить від паттерну взаємодії:
-
MethodChannel—виклик нативного методу та отримання результату (request/response) -
EventChannel—потік подій із нативного коду в Dart (підписки на hardware-события) -
BasicMessageChannel—двонаправлена передача довільних даних з кастомним кодеком
Типичний приклад: плагін для роботи з BLE-пристроєм. Сканування пристроїв—EventChannel (неперервний потік знайдених пристроїв). Підключення/відключення—MethodChannel. Отримання сповіщень від характеристики—знову EventChannel.
Структура плагіна
Створюємо через 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)
}
}
}
Передача складних типів
StandardMessageCodec (за замовчуванням) підтримує примітиви, List, Map. Для кастомних об'єктів—серіалізуємо в Map<String, dynamic> на Dart-стороні та отримуємо HashMap на Kotlin / [String: Any] на Swift. Альтернатива: Pigeon—інструмент від Flutter team для генерації type-safe API за .dart-специфікацією. Pigeon генерує Kotlin/Swift код з типізованими класами—виключає runtime-помилки від опечаток у іменах методів.
EventChannel та витоки пам'яті
При використанні EventChannel на Android нативна сторона отримує EventSink. Типова утічка: тримаємо EventSink в полі класу, Activity пересоздається при повороті екрана, старий EventSink не валідується—і виклик sink.success() після знищення викидає виключення. Рішення: обнулювати sink в onCancel() та перевіряти перед кожним вызовом.
Публікація та версионування
Для внутрішнього використання плагін живе в git-репозиторії та підключається через path або git залежність в pubspec.yaml. Для публікації на pub.dev—flutter pub publish з обов'язковим pubspec.yaml з homepage, repository, повним CHANGELOG.md.
Розробка плагіна: простий (1–2 методи, одна платформа)—2–4 дні. Повноцінний cross-platform з EventChannel, permissions та edge-case handling—2–4 тижні. Вартість розраховується індивідуально.







