Interakcja między kodem Dart a platformami natywnymi to kluczowe zadanie przy tworzeniu aplikacji Flutter wymagających dostępu do możliwości urządzenia. Według Flutter Team, 2026, Platform Channel pozostaje głównym mechanizmem takiej integracji, zapewniając przesyłanie wiadomości między Dart a natywnym kodem Android i iOS bez angażowania dodatkowych bibliotek natywnych.
Najważniejsze
Platform Channel to technologia Flutter zapewniająca dwukierunkową komunikację między kodem Dart aplikacji a natywnym kodem systemów operacyjnych Android i iOS. Bez Platform Channel aplikacja Flutter jest ograniczona do możliwości udostępnianych przez framework i nie może bezpośrednio uzyskiwać dostępu do API aparatu, czujników, Bluetooth, systemu plików i innych niskopoziomowych funkcji urządzenia.
Architektura Platform Channel opiera się na zasadzie asynchronicznej wymiany wiadomości. Strona Dart wysyła żądanie przez kanał, strona natywna je przetwarza i zwraca wynik. Wszystkie wiadomości są serializowane do formatu binarnego i przesyłane przez bufor wiadomości Flutter Engine, co zapewnia minimalne opóźnienie przy przesyłaniu danych między środowiskami wykonawczymi.
Każdy Platform Channel jest identyfikowany przez unikalną nazwę logiczną — ciąg znaków, który służy jako adres do routingu wiadomości. Strona Dart i strona natywna muszą używać tej samej nazwy kanału, aby komunikacja została nawiązana prawidłowo. Flutter obsługuje dowolną liczbę kanałów w jednej aplikacji, a każdy kanał działa niezależnie od pozostałych.
Według oficjalnej dokumentacji Flutter, Platform Channel przetwarza wiadomości w tej samej kolejności, w jakiej zostały wysłane, co gwarantuje przewidywalność sekwencji wywołań. Jest to krytyczne w scenariuszach, gdzie kolejność przetwarzania wpływa na poprawność działania, na przykład przy sekwencyjnej inicjalizacji modułów natywnych lub łańcuchu zależnych operacji.
Mechanizm przesyłania wiadomości przez Platform Channel składa się z trzech kluczowych warstw: strona Dart wysyła wiadomość w postaci Map lub List przez invokeMethod, Flutter Engine serializuje ją za pomocą StandardMethodCodec, a strona natywna otrzymuje wywołanie w swoim handlerze. Wynik wraca tą samą ścieżką w przeciwnym kierunku.
Proces serializacji automatycznie przekształca typy danych Dart w odpowiedniki na platformach natywnych. Liczby, ciągi znaków, wartości logiczne, listy i słowniki są obsługiwane bez dodatkowej konfiguracji ze strony dewelopera. Niestandardowe typy danych muszą być serializowane ręcznie, na przykład do ciągu JSON, przed wysłaniem przez kanał.
Po stronie Flutter Engine wiadomość trafia do kolejki głównego wątku platformy natywnej. W Android jest to główny wątek aplikacji, w iOS — główna pętla uruchomieniowa. Oznacza to, że długotrwałe operacje w handlerze kanału blokują interfejs użytkownika i prowadzą do zawieszeń. Deweloperom zaleca się wykonywanie ciężkich zadań w wątkach tła i zwracanie wyniku asynchronicznie przez callback.
Wydajność Platform Channel jest wystarczająco wysoka dla większości scenariuszy użycia: czas przesyłania jednej wiadomości wynosi mniej niż 1 milisekundę na nowoczesnych urządzeniach. Jednak w przypadku operacji o wysokim obciążeniu, takich jak przetwarzanie strumienia wideo w czasie rzeczywistym, zaleca się używanie Dart FFI lub natywnych wtyczek z bezpośrednim dostępem do pamięci urządzenia.
Kluczowe ograniczenie architektury: Platform Channel nie obsługuje przesyłania deskryptorów plików, wskaźników pamięci ani obiektów natywnych. Wszystkie dane muszą być serializowalne do formatu binarnego. Do przesyłania dużych ilości danych o objętości megabajtów używaj plików tymczasowych z przesyłaniem ścieżki do nich przez kanał.
Flutter udostępnia trzy typy Platform Channel, z których każdy jest przeznaczony do określonego scenariusza interakcji. Wybór odpowiedniego typu kanału określa architekturę integracji i wygodę utrzymania kodu po obu stronach — Dart i natywnej, dlatego ważne jest, aby zrozumieć różnice między MethodChannel, EventChannel i BasicMessageChannel.
MethodChannel to najczęściej używany typ Platform Channel, implementujący wzorzec zdalnego wywoływania procedur. Dart wysyła nazwę metody i argumenty, strona natywna wykonuje operację i zwraca wynik. Każde wywołanie zwraca Future, co umożliwia używanie konstrukcji async i await w kodzie Dart dla wygodnej pracy asynchronicznej.
Ten typ kanału nadaje się do operacji typu żądanie-odpowiedź: pobieranie poziomu naładowania baterii, odczytywanie danych z czujników, wykonywanie obliczeń po stronie natywnej lub żądanie danych z usług systemowych. MethodChannel obsługuje standardowe typy danych przez StandardMethodCodec, w tym wartości null dzięki obsłudze Null safety we współczesnym Dart.
W rzeczywistych projektach MethodChannel jest używany w większości oficjalnych wtyczek Flutter. Na przykład pakiety camera, battery i path_provider działają właśnie przez ten typ kanału, zapewniając dostęp do natywnych API bez konieczności pisania własnego kodu integracji dla każdej platformy.
EventChannel jest przeznaczony do scenariuszy, w których strona natywna generuje ciągły strumień zdarzeń w czasie. Dane są przesyłane do Dart przez Stream, umożliwiając subskrypcję aktualizacji w czasie rzeczywistym. Typowe przykłady użycia: odczyty akcelerometru, współrzędne GPS, zmiany stanu Bluetooth i powiadomienia z usług systemowych.
W przeciwieństwie do MethodChannel, EventChannel używa modelu publikacji-subskrypcji. Strona natywna wysyła zdarzenia w miarę ich występowania, bez jawnego żądania z kodu Dart. Subskrybent po stronie Dart otrzymuje każde zdarzenie w osobnym elemencie strumienia i może filtrować lub przekształcać otrzymane dane przed użyciem w interfejsie.
Podczas korzystania z EventChannel należy prawidłowo zarządzać subskrypcjami i ich anulowaniem. Każde wywołanie StreamSubscription powinno być anulowane po zakończeniu pracy z kanałem, aby uniknąć wycieku pamięci po stronie natywnej. Platforma Flutter automatycznie anuluje strumień przy zniszczeniu widżetu, ale jawne zarządzanie subskrypcjami zwiększa niezawodność aplikacji w długotrwałych scenariuszach.
BasicMessageChannel to najbardziej elastyczny typ Platform Channel, przeznaczony do dowolnej asynchronicznej wymiany wiadomości. W przeciwieństwie do MethodChannel, gdzie każda wiadomość zawiera nazwę metody i argumenty, BasicMessageChannel przesyła tylko ładunek użyteczny bez wbudowanego routingu. Strona wysyłająca przesyła wiadomość, strona odbierająca ją przetwarza i zwraca odpowiedź.
Ten typ kanału jest wygodny do niestandardowych protokołów interakcji, gdzie struktura wiadomości może się dynamicznie zmieniać w zależności od stanu aplikacji. BasicMessageChannel domyślnie używa StandardMessageCodec, ale obsługuje podstawienie dowolnego MessageCodec dla niestandardowych formatów serializacji danych.
W praktyce BasicMessageChannel jest używany rzadziej niż MethodChannel, ponieważ wymaga ręcznego przetwarzania routingu wiadomości bez wbudowanego wzorca nazewnictwa. Jest jednak niezastąpiony przy integracji z bibliotekami natywnymi, które oczekują określonego formatu wiadomości, innego niż standardowy wzorzec żądanie-odpowiedź zaimplementowany w MethodChannel.
Rozważmy praktyczną implementację Platform Channel na przykładzie pobierania poziomu naładowania baterii urządzenia. Ten przykład demonstruje pełny cykl pracy: deklarację MethodChannel po stronie Dart, implementację handlera na Android i iOS, a także prawidłowe obsługiwanie błędów przy niedostępności danych lub braku wymaganych uprawnień.
Po stronie Dart tworzona jest instancja MethodChannel z unikalną nazwą kanału w postaci ciągu znaków. Metoda invokeMethod wysyła żądanie na stronę natywną i oczekuje wyniku w postaci Future. Obsługa błędów odbywa się przez przechwytywanie PlatformException, które strona natywna zwraca w przypadku wystąpienia wyjątku podczas przetwarzania żądania.
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}';
}
}
}
Po stronie Android handler jest rejestrowany w MainActivity przez metodę configureFlutterEngine. Wewnątrz setMethodCallHandler sprawdzana jest nazwa przychodzącej metody, wykonywane jest natywne wywołanie BatteryManager w celu uzyskania poziomu naładowania, a wynik jest zwracany przez obiekt result. Dla metod nieobsługiwanych przez kanał wywoływane jest 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
)
}
}
Na platformie iOS handler jest rejestrowany w klasie AppDelegate przez FlutterMethodChannel. Kod Swift otrzymuje przychodzące wywołanie, odwołuje się do systemowego API UIDevice w celu uzyskania poziomu naładowania baterii i zwraca wynik do Flutter. Asynchroniczne przetwarzanie z przechwyceniem weak self umożliwia wykonywanie żądań bez ryzyka utrzymania cyklu silnych referencji w pamięci.
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 jest niezbędny w każdym przypadku, gdy aplikacja Flutter wymaga dostępu do możliwości urządzenia niezaimplementowanych w standardowych pakietach. Deweloper powinien utworzyć własny kanał przy integracji z natywnymi SDK do aparatu, biometrii, NFC, Bluetooth Low Energy lub przy pracy z systemem plików poza piaskownicą aplikacji.
Pierwszy typowy scenariusz — korzystanie z natywnych API, do których nie ma bezpośredniego dostępu z Dart. Obejmuje to usługi systemowe Android i iOS, czujniki sprzętowe z niestandardowymi protokołami przesyłania danych, push notifications z niestandardową logiką przetwarzania oraz operacje kryptograficzne wymagające użycia Hardware Security Module do bezpiecznego przechowywania kluczy.
Drugi scenariusz — integracja istniejącego kodu natywnego z projektem Flutter. Jeśli firma już opracowała natywną bibliotekę dla Android lub iOS, Platform Channel umożliwia jej ponowne wykorzystanie bez portowania na Dart. Przyspiesza to migrację aplikacji hybrydowych na Flutter i zachowuje inwestycje w istniejący kod natywny oraz nagromadzoną logikę biznesową.
Trzeci scenariusz — publikacja własnej wtyczki Flutter w pub.dev. Wszystkie popularne wtyczki używają Platform Channel do udostępniania jednolitego API w Dart, które pod spodem wywołuje kod natywny każdej platformy. Jest to standardowe podejście zalecane przez zespół Flutter do tworzenia wielokrotnie używalnych pakietów z obsługą obu platform mobilnych.
Przy wyborze między utworzeniem własnego Platform Channel a użyciem gotowego pakietu z pub.dev zaleca się najpierw sprawdzenie dostępności gotowego rozwiązania. Pakiety camera, geolocator, shared_preferences i path_provider pokrywają większość typowych potrzeb. Własny Platform Channel jest uzasadniony tylko w przypadku braku odpowiedniego pakietu lub konieczności głębokiej personalizacji zachowania natywnego, której nie zapewnia istniejące rozwiązanie.
Często zadawane pytania
MethodChannel implementuje wzorzec żądanie-odpowiedź z jednorazowym wywołaniem metody i zwróceniem wyniku przez Future. EventChannel używa modelu strumieniowego: strona natywna wysyła zdarzenia w miarę ich występowania, a Dart otrzymuje je przez Stream. MethodChannel nadaje się do jednorazowych operacji z oczekiwaniem na wynik, EventChannel — do ciągłych strumieni danych w czasie rzeczywistym.
Platform Channel obsługuje podstawowe typy Dart: int, double, bool, String, List i Map. Te typy są automatycznie serializowane do natywnych odpowiedników przez StandardMethodCodec i StandardMessageCodec bez udziału dewelopera. Do przesyłania niestandardowych obiektów wymagana jest ręczna serializacja do JSON lub użycie dowolnego MessageCodec z obsługą niestandardowych formatów.
Tak, Flutter obsługuje nieograniczoną liczbę Platform Channel w jednej aplikacji. Każdy kanał jest identyfikowany przez unikalną nazwę w postaci ciągu znaków, która musi być zgodna po stronie Dart i platformy natywnej. Można tworzyć osobne kanały dla różnych modułów: jeden dla aparatu, drugi dla Bluetooth, trzeci dla czujników — wszystkie działają niezależnie i nie wpływają na swoją wydajność.
Po stronie Dart błędy są obsługiwane przez PlatformException, który strona natywna zwraca w przypadku wystąpienia wyjątku. Blok try-catch przechwytuje wyjątek i udostępnia kod, komunikat i szczegóły błędu. Po stronie natywnej wywołanie result.error wysyła błąd z powrotem do Dart. Dostępna jest również metoda result.notImplemented dla metod nieobsługiwanych przez kanał.
Tak, handler Platform Channel wykonuje się na głównym wątku platformy natywnej. Jeśli handler wykonuje długotrwałą operację — żądanie sieciowe, odczyt z dysku lub ciężkie obliczenia — interfejs użytkownika może się zawiesić. Zaleca się uruchamianie ciężkich zadań w wątku tła po stronie natywnej i wywoływanie result dopiero po zakończeniu. Strona Dart przy tym nie jest blokowana dzięki asynchronicznej naturze invokeMethod.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.