Canale de platformă: integrarea codului nativ Swift și Kotlin
Flutter este excelent pentru 90% dintre funcțiile aplicației, dar ele există cazuri în care aveți nevoie de acces direct la API-urile native ale platformei: senzori biometrici, Bluetooth LE, Apple Pay, Google Pay, acces la sistemul de fișiere nativ, integrarea cu SDK-uri terță parte nu este disponibilă ca plugin-uri Flutter. Pentru aceste cazuri există Canale de platformă.
Canalele platformei creează o punte de comunicare bidirecțională între Darts și cod nativ (Swift pe iOS, Kotlin pe Android). Nu scrii Java o Legacy Objective-C: Modern Kotlin și Swift, cu corutine și async/wait respectiv. În acest ghid construim un plugin complet care accesează la senzorul de proximitate al dispozitivului — nu este disponibil în Flutter core.
Ce vei învăța
- MethodChannel: Dart → apeluri native cu răspuns (cerere/răspuns)
- EventChannel: flux continuu de date din nativ → Dart
- BasicMessageChannel: comunicare bidirecțională nestructurată
- Gestionarea erorilor: PlatformException din ambele direcții
- Threading: fir principal, fir de fundal, FlutterEngine
- Pachet de pluginuri: Structură pentru pluginuri reutilizabile
- Pigeon: generare de cod sigur de tip pentru canalele platformei
MethodChannel: Cerere/Răspuns sincron
Il MethodChannel și cel mai comun tip de canal: codul Dart apelează o metodă nativă și așteaptă un răspuns. Ideal pentru operațiuni dintr-o singură lovitură cum să obțineți informații despre dispozitiv, să deschideți un ecran nativ, efectuează o operație biometrică.
// 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: flux de date continuu
L'EventChannel și concepute pentru streaming de date: senzori care se actualizează continuu, evenimente de geolocalizare, notificări hardware. Nativul emite evenimente într-un flux Dart pe care widget-ul le poate asculta StreamBuilder.
// 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
}
}
Porumbel: generarea codului tip sigur
Porumbel și instrumentul oficial Flutter pentru a genera codul Platforma Canale standard dintr-o definiție Dart comună. Elimină necesitatea de a gestiona serializarea manuală și garanțiile că Dart și codul nativ sunt sincronizate.
// 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
Când să utilizați canalele platformei față de pluginurile existente
- Înainte de a scrie Platform Channels: căutați pub.dev pentru a vedea dacă există deja un plugin
- pub.dev are peste 40.000 de pachete: majoritatea API-urilor native sunt deja acoperite
- Utilizați canalele platformei pentru: SDK-uri proprietare pentru întreprinderi, API-uri foarte specifice, integrare cu codul moștenit nativ existent
- Pentru noile pluginuri care pot fi partajate: utilizați Pigeon pentru siguranța tipului și pentru mai puțină întreținere
Concluzii
Canalele platformei sunt mecanismul care face ca Flutter să fie cu adevărat universal: orice API nativ și accesibil cu MethodChannel sau EventChannel. Porumbel adaugă siguranță de tip și îndepărtează o mare parte din placa de caz. Cheia pentru bună integrare nativă și gestionați corect erorile cu PlatformException și curățați resurse (anulați înregistrarea ascultătorului, anulați fluxul) în ciclul de viață corect.







