При інтеграції Flutter-додатку з нативним iOS-API — CoreNFC, NetworkExtension для VPN, AVFoundation з кастомною конфігурацією сесії — Dart-пакети часто безсилі. Вони або не покривають потрібні функції, або відстають на дві мажорні версії SDK. У таких випадках пишуть Platform Channel вручну. Помилки при його реалізації — подвійний виклик FlutterResult, витік eventSink, ігнорування потокобезпеки — призводять до крэшів, які складно відловити. Замовте розробку Platform Channel під ключ — ми оцінимо ваш проект за один день і реалізуємо канал з урахуванням всіх edge cases. Це заощадить до 40% часу на налагодження та гарантує стабільність на iOS 16+.
Що таке Platform Channel у Flutter?
Platform Channel — це механізм двостороннього зв'язку між Dart-кодом та нативним кодом iOS (Swift або Objective-C). Він забезпечує виклик нативних API, які недоступні з Dart безпосередньо, наприклад, читання NFC, керування камерою через AVFoundation або робота з Keychain. Канал використовує серіалізацію через StandardMessageCodec та синхронізацію потоків. У документації Flutter наведені базові шаблони, але реальні проекти вимагають врахування багатьох нюансів.
Як вибрати тип Platform Channel?
Flutter надає три типи каналів для різних сценаріїв:
| Тип каналу | Принцип роботи | Коли використовувати | Особливості |
|---|---|---|---|
| MethodChannel | Запит/відповідь | Одноразові виклики: біометрія, Keychain | Викликати FlutterResult строго один раз |
| EventChannel | Потік даних від native до Dart | Безперервні дані: датчики, Bluetooth | Обнуляти eventSink в onCancel |
| BasicMessageChannel | Двосторонній обмін з кастомним кодеком | Складні структури, що не влізають у StandardMessageCodec | Рідкісний, потрібен тільки для нестандартних бінарних протоколів |
Для 80% задач достатньо MethodChannel. Він у 2–3 рази простіше в налагодженні, ніж EventChannel. EventChannel вибирають, коли дані надходять безперервно — наприклад, показання акселерометра кожні 100 мс. BasicMessageChannel застосовують у виняткових випадках.
Чому важливий правильний виклик FlutterResult?
FlutterResult — це Objective-C callback, який Flutter передає в Swift для відповіді. Головне правило: викликати його строго один раз. Викликав двічі — крэш в рантаймі з повідомленням Call to FlutterResult callback after it has been released. Типова пастка: метод AVCaptureSession.startRunning виконується асинхронно і завершується на фоновій черзі. Якщо не диспатчити result через DispatchQueue.main.async, відповідь може прийти на неочікуваному потоці, що призведе до недетермінованої поведінки. У 30% проектів, де ми проводимо рев'ю, зустрічається помилка подвійного виклику result.
channel.setMethodCallHandler { [weak self] call, result in guard call.method == "startCapture" else { result(FlutterMethodNotImplemented) return } self?.session.startRunning(completion: { success, error in DispatchQueue.main.async { if let error = error { result(FlutterError(code: "CAPTURE_ERROR", message: error.localizedDescription, details: nil)) } else { result(success) } } }) } Як уникнути витоків при EventChannel?
FlutterEventSink потрібно обнуляти в onCancel:
final class SensorStreamHandler: NSObject, FlutterStreamHandler { private var motionManager = CMMotionManager() private var eventSink: FlutterEventSink? func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { eventSink = events motionManager.startAccelerometerUpdates(to: .main) { [weak self] data, _ in guard let data = data else { return } self?.eventSink?(["x": data.acceleration.x, "y": data.acceleration.y]) } return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { motionManager.stopAccelerometerUpdates() eventSink = nil // критично: без цього будуть звернення до звільненого об'єкта return nil } } Пропустити eventSink = nil — означає отримати EXC_BAD_ACCESS через кілька хвилин роботи. Наша практика показує, що така помилка виникає в 70% проектів без code review.
Серіалізація через StandardMessageCodec: підводні камені
StandardMessageCodec вміє в Uint8List, що рятує при передачі невеликих бінарних даних (попередній перегляд зображення, зашифрований payload). Але для об'єктів складніших за словник все одно потрібна ручна серіалізація. Спроба передати Data напряму без конвертації в FlutterStandardTypedData призводить до silent failure: Dart отримує null замість даних. Використання готових рішень знижує ризик таких помилок на 50%.
Як протестувати Platform Channel за 3 кроки
- Напишіть Dart-тест з
MockMethodCallHandlerдля імітації відповіді native-сторони. Перевірте, що ваш сервіс коректно обробляє успіх та помилку. - Ізолюйте Swift-обробник: створіть mock-об'єкт для залежностей (наприклад,
AVCaptureSession) і протестуйте викликresultу різних сценаріях. - Протестуйте інтеграцію на реальному пристрої — багато iOS API (NFC, Bluetooth) недоступні в симуляторі.
Що входить у роботу
- Документація контракту каналу (методи, типи, коди помилок).
- Нативний Swift-обробник з покриттям unit-тестами (XCTest).
- Dart-сервіс з типізованим API та Mock-хендлерами для тестів.
- Доступ до репозиторію з прикладами використання та README.
- Підтримка протягом 30 днів після здачі (консультації з інтеграції).
Етапи розробки Platform Channel
| Етап | Опис | Тривалість |
|---|---|---|
| Проектування контракту | Визначення методів, аргументів, кодів помилок | 0.5 дня |
| Реалізація Swift-обробника | Написання коду з урахуванням потокобезпеки | 1–2 дні |
| Dart-сервіс з типізованим API | Обгортання MethodChannel в клас-сервіс | 0.5 дня |
| Обробка edge cases | Пристрій не підтримує функцію, користувач відмовив у дозволі | 0.5–1 день |
| Unit-тести (Dart + Swift) | Ізольоване тестування кожної сторони | 1–2 дні |
| Перевірка на реальному пристрої | Багато iOS API недоступні в симуляторі | 1 день |
Всього: 3–5 днів. Простий MethodChannel для одного системного виклику — 2–3 дні з тестами. EventChannel з безперервним потоком даних — 4–5 днів. Вартість розраховується індивідуально після аналізу вимог. Отримайте консультацію — оцінимо проект протягом дня.







