Взаємодія між Dart-кодом і нативними платформами — ключове завдання під час розробки Flutter-застосунків, які потребують доступу до можливостей пристрою. Згідно з Flutter Team, 2026, Platform Channel залишається основним механізмом для такої інтеграції, забезпечуючи передавання повідомлень між Dart і нативним кодом Android та iOS без залучення додаткових нативних бібліотек.
Головне
Platform Channel — це технологія Flutter, що забезпечує двосторонній зв'язок між Dart-кодом застосунку та нативним кодом операційних систем Android та iOS. Без Platform Channel застосунок Flutter обмежений можливостями, які надає фреймворк, і не може безпосередньо звертатися до API камери, датчиків, Bluetooth, файлової системи та інших низькорівневих функцій пристрою.
Архітектура Platform Channel побудована за принципом асинхронного обміну повідомленнями. Сторона Dart надсилає запит через канал, нативна сторона обробляє його та повертає результат. Усі повідомлення серіалізуються у двійковий формат і передаються через буфер повідомлень Flutter Engine, що забезпечує мінімальну затримку під час передавання даних між середовищами виконання.
Кожен Platform Channel ідентифікується унікальним логічним іменем — рядком, який слугує адресою для маршрутизації повідомлень. Сторона Dart і нативна сторона повинні використовувати однакове ім'я каналу, щоб зв'язок встановився коректно. Flutter підтримує довільну кількість каналів в одному застосунку, і кожен канал працює незалежно від інших.
Згідно з офіційною документацією Flutter, Platform Channel обробляє повідомлення в тому ж порядку, в якому вони були надіслані, що гарантує передбачуваність послідовності викликів. Це критично важливо для сценаріїв, де порядок обробки впливає на коректність роботи, наприклад, при послідовній ініціалізації нативних модулів або ланцюжку залежних операцій.
Механізм передавання повідомлень через Platform Channel складається з трьох ключових шарів: сторона Dart надсилає повідомлення у вигляді Map або List через invokeMethod, Flutter Engine серіалізує його за допомогою StandardMethodCodec, і нативна сторона отримує виклик у своєму обробнику. Результат повертається тим же шляхом у зворотному напрямку.
Процес серіалізації автоматично перетворює типи даних Dart на еквіваленти нативних платформ. Числа, рядки, булеві значення, списки та словники підтримуються без додаткової конфігурації з боку розробника. Користувацькі типи даних необхідно серіалізувати вручну, наприклад, у JSON-рядок, перед відправленням через канал.
На стороні Flutter Engine повідомлення потрапляє в чергу головного потоку нативної платформи. В Android це головний потік застосунку, в iOS — головний цикл виконання. Це означає, що тривалі операції в обробнику каналу блокують інтерфейс користувача та призводять до зависань. Розробникам рекомендується виконувати важкі завдання у фонових потоках і повертати результат асинхронно через callback.
Продуктивність Platform Channel достатньо висока для більшості сценаріїв використання: час передавання одного повідомлення становить менше ніж 1 мілісекунда на сучасних пристроях. Однак для високонавантажених операцій, таких як обробка відеопотоку в реальному часі, рекомендується використовувати Dart FFI або нативні плагіни з прямим доступом до пам'яті пристрою.
Ключове обмеження архітектури: Platform Channel не підтримує передавання файлових дескрипторів, покажчиків пам'яті або нативних об'єктів. Усі дані мають бути серіалізовані у двійковий формат. Для передавання великих обсягів даних об'ємом у мегабайти використовуйте тимчасові файли з передаванням шляху до них через канал.
Flutter надає три типи Platform Channel, кожен з яких призначений для певного сценарію взаємодії. Вибір правильного типу каналу визначає архітектуру інтеграції та зручність підтримки коду на обох сторонах — Dart і нативній, тому важливо розуміти відмінності між MethodChannel, EventChannel та BasicMessageChannel.
MethodChannel — найпоширеніший тип Platform Channel, що реалізує патерн віддаленого виклику процедур. Dart надсилає ім'я методу та аргументи, нативна сторона виконує операцію та повертає результат. Кожен виклик повертає Future, що дозволяє використовувати конструкції async та await у Dart-коді для зручної асинхронної роботи.
Цей тип каналу підходить для операцій типу запит-відповідь: отримання рівня заряду батареї, читання показників сенсорів, виконання розрахунків на нативній стороні або запит даних із системних сервісів. MethodChannel підтримує стандартні типи даних через StandardMethodCodec, включаючи null-значення завдяки підтримці Null safety у сучасному Dart.
У реальних проєктах MethodChannel використовується в більшості офіційних плагінів Flutter. Наприклад, пакети camera, battery та path_provider працюють саме через цей тип каналу, забезпечуючи доступ до нативних API без необхідності писати власний код інтеграції для кожної платформи.
EventChannel призначений для сценаріїв, де нативна сторона генерує безперервний потік подій у часі. Дані передаються в Dart через Stream, дозволяючи підписуватися на оновлення в реальному часі. Типові приклади використання: показники акселерометра, GPS-координати, зміни стану Bluetooth та сповіщення від системних сервісів.
На відміну від MethodChannel, EventChannel використовує модель публікації-підписки. Нативна сторона надсилає події в міру їх виникнення, без явного запиту від Dart-коду. Підписник на стороні Dart отримує кожну подію в окремому елементі стріму та може фільтрувати або трансформувати отримані дані перед використанням в інтерфейсі.
При використанні EventChannel необхідно правильно керувати підписками та їх скасуванням. Кожен виклик StreamSubscription має бути скасований при завершенні роботи з каналом, щоб уникнути витоку пам'яті на нативній стороні. Платформа Flutter автоматично скасовує стрім при знищенні віджета, але явне керування підписками підвищує надійність застосунку в довготривалих сценаріях.
BasicMessageChannel — найбільш гнучкий тип Platform Channel, призначений для довільного асинхронного обміну повідомленнями. На відміну від MethodChannel, де кожне повідомлення містить ім'я методу та аргументи, BasicMessageChannel передає лише корисне навантаження без вбудованої маршрутизації. Сторона-відправник надсилає повідомлення, сторона-отримувач обробляє його та повертає відповідь.
Цей тип каналу зручний для кастомних протоколів взаємодії, де структура повідомлень може змінюватися динамічно залежно від стану застосунку. BasicMessageChannel використовує StandardMessageCodec за замовчуванням, але підтримує підстановку довільного MessageCodec для нестандартних форматів серіалізації даних.
На практиці BasicMessageChannel застосовується рідше за MethodChannel, оскільки вимагає ручної обробки маршрутизації повідомлень без вбудованого патерну іменування. Однак він незамінний при інтеграції з нативними бібліотеками, які очікують певний формат повідомлень, відмінний від стандартного патерну запит-відповідь, реалізованого в MethodChannel.
Розглянемо практичну реалізацію Platform Channel на прикладі отримання рівня заряду батареї пристрою. Цей приклад демонструє повний цикл роботи: оголошення MethodChannel на стороні Dart, реалізацію обробника на Android та iOS, а також коректну обробку помилок при недоступності даних або відсутності необхідних дозволів.
На стороні Dart створюється екземпляр MethodChannel з унікальним рядковим іменем каналу. Метод invokeMethod надсилає запит на нативну сторону та очікує результат у вигляді Future. Обробка помилок виконується через перехоплення PlatformException, який нативна сторона повертає при виникненні винятку в процесі обробки запиту.
import 'package:flutter/services.dart';
class BatteryPlugin {
static const _channel = MethodChannel(
'samples.flutter.dev/battery',
);
Future<String> getBatteryLevel() async {
try {
final result = await _channel.invokeMethod<int>(
'getBatteryLevel',
);
return 'Battery level: $result%';
} on PlatformException catch (e) {
return 'Failed: ${e.message}';
}
}
}
На стороні Android обробник реєструється в MainActivity через метод configureFlutterEngine. Всередині setMethodCallHandler перевіряється ім'я вхідного методу, виконується нативний виклик BatteryManager для отримання рівня заряду, і результат повертається через об'єкт result. Для методів, не підтримуваних каналом, викликається result.notImplemented.
import android.os.BatteryManager
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(
flutterEngine: FlutterEngine
) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val level = getBatteryLevel()
if (level != -1) {
result.success(level)
} else {
result.error(
"UNAVAILABLE",
"Battery level not available",
null
)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val manager = getSystemService(BATTERY_SERVICE) as BatteryManager
return manager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
)
}
}
На платформі iOS обробник реєструється в класі AppDelegate через FlutterMethodChannel. Swift-код отримує вхідний виклик, звертається до системного API UIDevice для отримання рівня заряду батареї та повертає результат у Flutter. Асинхронна обробка із захопленням weak self дозволяє виконувати запити без ризику утримання циклу сильних посилань у пам'яті.
import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
override func application(
application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(
name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { [weak self] call, result in
if call.method == "getBatteryLevel" {
let level = self?.getBatteryLevel() ?? -1
if level >= 0 {
result(level)
} else {
result(FlutterError(
code: "UNAVAILABLE",
message: "Battery level not available",
details: nil
))
}
} else {
result(FlutterMethodNotImplemented)
}
}
return super.application(
application: application,
didFinishLaunchingWithOptions: launchOptions
)
}
private func getBatteryLevel() -> Int {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
return Int(device.batteryLevel * 100)
}
}
Platform Channel необхідний у кожному випадку, коли застосунок Flutter потребує доступу до можливостей пристрою, не реалізованих у стандартних пакетах. Розробнику слід створювати власний канал при інтеграції з нативними SDK для камери, біометрії, NFC, Bluetooth Low Energy або при роботі з файловою системою за межами пісочниці застосунку.
Перший типовий сценарій — використання нативних API, до яких немає прямого доступу з Dart. Сюди входять системні сервіси Android та iOS, апаратні датчики з нестандартними протоколами передавання даних, push-сповіщення з кастомною логікою обробки та криптографічні операції, що потребують використання Hardware Security Module для безпечного зберігання ключів.
Другий сценарій — інтеграція існуючого нативного коду в проєкт Flutter. Якщо компанія вже розробила нативну бібліотеку для Android або iOS, Platform Channel дозволяє перевикористовувати її без портування на Dart. Це прискорює міграцію гібридних застосунків на Flutter та зберігає інвестиції в існуючий нативний код і накопичену бізнес-логіку.
Третій сценарій — публікація власного плагіна Flutter на pub.dev. Усі популярні плагіни використовують Platform Channel для надання єдиного API на Dart, який під капотом викликає нативний код кожної платформи. Це стандартний підхід, рекомендований командою Flutter для створення перевикористовуваних пакетів із підтримкою обох мобільних платформ.
При виборі між створенням власного Platform Channel та використанням готового пакета з pub.dev рекомендується спочатку перевірити доступність готового рішення. Пакети camera, geolocator, shared_preferences та path_provider покривають більшість типових потреб. Власний Platform Channel виправданий лише за відсутності відповідного пакета або при необхідності глибокої кастомізації нативної поведінки, яку не забезпечує існуюче рішення.
Поширені запитання
MethodChannel реалізує патерн запит-відповідь з одноразовим викликом методу та поверненням результату через Future. EventChannel використовує потокову модель: нативна сторона надсилає події в міру виникнення, а Dart отримує їх через Stream. MethodChannel підходить для разових операцій з очікуванням результату, EventChannel — для безперервних потоків даних у реальному часі.
Platform Channel підтримує базові типи Dart: int, double, bool, String, List та Map. Ці типи автоматично серіалізуються в нативні еквіваленти через StandardMethodCodec та StandardMessageCodec без участі розробника. Для передавання користувацьких об'єктів потрібна ручна серіалізація в JSON або використання довільного MessageCodec із підтримкою нестандартних форматів.
Так, Flutter підтримує необмежену кількість Platform Channel в одному застосунку. Кожен канал ідентифікується унікальним рядковим іменем, яке має збігатися на стороні Dart та нативної платформи. Можна створювати окремі канали для різних модулів: один для камери, інший для Bluetooth, третій для датчиків — усі вони працюють незалежно та не впливають на продуктивність один одного.
На стороні Dart помилки обробляються через PlatformException, який нативна сторона повертає при виникненні винятку. Блок try-catch перехоплює виняток і надає доступ до коду, повідомлення та деталей помилки. На нативній стороні виклик result.error надсилає помилку назад у Dart. Також доступний метод result.notImplemented для методів, не підтримуваних каналом.
Так, обробник Platform Channel виконується на основному потоці нативної платформи. Якщо обробник виконує тривалу операцію — мережевий запит, читання з диска або важкі обчислення — інтерфейс користувача може зависнути. Рекомендується запускати важкі завдання у фоновому потоці на нативній стороні та викликати result лише після завершення. Сторона Dart при цьому не блокується завдяки асинхронній природі invokeMethod.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.