Kanały platformy: Integracja kodu natywnego Swift i Kotlin
Flutter świetnie nadaje się do 90% funkcji aplikacji, ale one istnieją przypadki, w których potrzebujesz bezpośredniego dostępu do natywnych API platformy: czujniki biometryczne, Bluetooth LE, Apple Pay, Google Pay, dostęp do systemu plików natywny, integracja z zestawami SDK innych firm nie jest dostępna jako wtyczki Flutter. W takich przypadkach istnieją Kanały platformy.
Kanały Platformy tworzą dwukierunkowy pomost komunikacyjny pomiędzy Dartami oraz kod natywny (Swift na iOS, Kotlin na Androidzie). Nie piszesz w Javie o Starszy cel-C: nowoczesny Kotlin i Swift, z współprogramami i async/await odpowiednio. W tym przewodniku tworzymy kompletną wtyczkę, która umożliwia dostęp do czujnika zbliżeniowego urządzenia – niedostępne w Flutter Core.
Czego się nauczysz
- MethodChannel: Dart → natywne wywołania z odpowiedzią (żądanie/odpowiedź)
- EventChannel: Ciągły strumień danych z natywnego → Darta
- BasicMessageChannel: nieustrukturyzowana komunikacja dwukierunkowa
- Obsługa błędów: wyjątek PlatformException z obu stron
- Threading: wątek główny, wątek tła, FlutterEngine
- Pakiet wtyczek: Struktura wtyczek wielokrotnego użytku
- Pigeon: generowanie kodu bezpiecznego dla typów dla kanałów platformy
MethodChannel: Synchroniczne żądanie/odpowiedź
Il MetodaKanał i najpopularniejszy typ kanału: kod Dart wywołuje metodę natywną i czeka na odpowiedź. Idealny do jednorazowych operacji jak uzyskać informacje o urządzeniu, otworzyć natywny ekran, wykonać operację biometryczną.
// lib/proximity_sensor.dart: interfaccia Dart
import 'package:flutter/services.dart';
class ProximitySensor {
static const _channel = MethodChannel('com.example.proximity_sensor');
// Verifica se il sensore e disponibile
static Future<bool> isAvailable() async {
try {
final bool result = await _channel.invokeMethod('isAvailable');
return result;
} on PlatformException catch (e) {
// PlatformException: errore dal codice nativo
debugPrint('ProximitySensor.isAvailable error: ${e.code} - ${e.message}');
return false;
} on MissingPluginException {
// Plugin non registrato (es. desktop non supporta il sensore)
return false;
}
}
// Ottieni la distanza attuale (in cm, 0 = vicino, null = non disponibile)
static Future<double?> getDistance() async {
try {
final double? distance = await _channel.invokeMethod<double>('getDistance');
return distance;
} on PlatformException catch (e) {
throw ProximitySensorException(e.code, e.message ?? 'Unknown error');
}
}
}
class ProximitySensorException implements Exception {
final String code;
final String message;
const ProximitySensorException(this.code, this.message);
@override
String toString() => 'ProximitySensorException($code): $message';
}
// android/src/main/kotlin/.../ProximitySensorPlugin.kt
// Implementazione Android con Kotlin
package com.example.proximity_sensor
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorManager
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class ProximitySensorPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private lateinit var channel: MethodChannel
private lateinit var context: Context
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
context = binding.applicationContext
channel = MethodChannel(
binding.binaryMessenger,
"com.example.proximity_sensor"
)
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"isAvailable" -> {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
result.success(sensor != null)
}
"getDistance" -> {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
if (sensor == null) {
// Errore: ritorna PlatformException a Dart
result.error(
"SENSOR_NOT_FOUND",
"Proximity sensor not available on this device",
null
)
return
}
// Leggi il valore attuale (semplificato: usa EventChannel per stream)
result.success(sensor.maximumRange.toDouble())
}
else -> result.notImplemented()
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
// ios/Classes/ProximitySensorPlugin.swift
// Implementazione iOS con Swift
import Flutter
import UIKit
public class ProximitySensorPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "com.example.proximity_sensor",
binaryMessenger: registrar.messenger()
)
let instance = ProximitySensorPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "isAvailable":
// iOS ha sempre il sensore di prossimita nei modelli standard
result(UIDevice.current.isProximityMonitoringEnabled ||
ProcessInfo.processInfo.environment["SIMULATOR_DEVICE_NAME"] == nil)
case "getDistance":
UIDevice.current.isProximityMonitoringEnabled = true
let isNear = UIDevice.current.proximityState
// Conversione in float: 0.0 = lontano, 1.0 = vicino
let distance: Double = isNear ? 0.0 : 10.0
result(distance)
default:
result(FlutterMethodNotImplemented)
}
}
}
EventChannel: ciągły strumień danych
L'Kanał wydarzenia i przeznaczone do przesyłania strumieniowego danych: czujniki które są stale aktualizowane, zdarzenia geolokalizacyjne, powiadomienia sprzętowe. Natywny emituje zdarzenia do strumienia Dart, którego widget może słuchać Konstruktor strumieni.
// Dart: EventChannel per stream del sensore di prossimita
class ProximitySensorStream {
static const _eventChannel = EventChannel('com.example.proximity_sensor/stream');
static Stream<double> get proximityStream {
return _eventChannel
.receiveBroadcastStream()
.map((event) => (event as double));
}
}
// Widget: StreamBuilder per visualizzare i dati in tempo reale
class ProximitySensorWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<double>(
stream: ProximitySensorStream.proximityStream,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Errore: ${snapshot.error}');
}
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
final distance = snapshot.data!;
return Column(
children: [
Text('Distanza: ${distance.toStringAsFixed(1)} cm'),
Icon(
distance < 5 ? Icons.phone_in_talk : Icons.phone_android,
size: 48,
color: distance < 5 ? Colors.red : Colors.green,
),
],
);
},
);
}
}
// Kotlin: EventChannel sul lato Android
class ProximitySensorStreamHandler(
private val context: Context
) : EventChannel.StreamHandler {
private var sensorManager: SensorManager? = null
private var sensorListener: SensorEventListener? = null
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager?.getDefaultSensor(Sensor.TYPE_PROXIMITY)
if (sensor == null) {
events.error("SENSOR_NOT_FOUND", "No proximity sensor", null)
return
}
sensorListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
// Invia ogni aggiornamento al Dart stream
events.success(event.values[0].toDouble())
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
sensorManager?.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onCancel(arguments: Any?) {
// Pulisci le risorse quando lo stream viene cancellato
sensorManager?.unregisterListener(sensorListener)
sensorManager = null
sensorListener = null
}
}
Pigeon: Bezpieczne generowanie kodu
Gołąb oraz oficjalne narzędzie Flutter do generowania kodu Schemat wzorcowy Platform Channels na podstawie udostępnionej definicji Dart. Eliminuje potrzebę zarządzania ręczną serializacją i gwarancjami że Dart i kod natywny są zsynchronizowane.
// pigeons/proximity.dart: definizione dell'interfaccia (source of truth)
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/proximity_api.g.dart',
kotlinOut: 'android/src/main/kotlin/com/example/ProximityApi.g.kt',
swiftOut: 'ios/Classes/ProximityApi.g.swift',
))
// Messaggi (data classes)
class ProximityInfo {
final double distance;
final bool isNear;
const ProximityInfo({required this.distance, required this.isNear});
}
// HostApi: metodi implementati nel codice nativo, chiamati da Dart
@HostApi()
abstract class ProximityHostApi {
bool isAvailable();
ProximityInfo getCurrentReading();
}
// FlutterApi: metodi implementati in Dart, chiamati dal nativo
@FlutterApi()
abstract class ProximityFlutterApi {
void onProximityChanged(ProximityInfo info);
}
// Genera il codice:
// flutter pub run pigeon --input pigeons/proximity.dart
Kiedy używać kanałów platformy a kiedy istniejących wtyczek
- Przed napisaniem kanałów platformy: wyszukaj pub.dev, aby sprawdzić, czy wtyczka już istnieje
- pub.dev ma ponad 40 000 pakietów: większość natywnych interfejsów API jest już uwzględniona
- Użyj kanałów platformy do: zastrzeżonych zestawów SDK dla przedsiębiorstw, bardzo specyficznych interfejsów API, integracji z istniejącym, starszym kodem natywnym
- W przypadku nowych wtyczek, które można udostępniać: użyj Pigeon, aby zapewnić bezpieczeństwo typów i mniej konserwacji
Wnioski
Kanały platformy to mechanizm, który sprawia, że Flutter jest naprawdę uniwersalny: dowolny natywny interfejs API i osiągalny za pomocą MethodChannel lub EventChannel. Gołąb dodaje bezpieczeństwo typu i usuwa większość schematów. Klucz do dobra natywna integracja i odpowiednia obsługa błędów PlatformException i wyczyść zasoby (wyrejestruj słuchacza, anuluj transmisję) w prawidłowym cyklu życia.







