プラットフォーム チャネル: Swift と Kotlin ネイティブ コードの統合
Flutter はアプリの機能の 90% に最適ですが、それらは存在します プラットフォームのネイティブ API に直接アクセスする必要がある場合: 生体認証センサー、Bluetooth LE、Apple Pay、Google Pay、ファイル システム アクセス ネイティブ、サードパーティ SDK との統合は Flutter プラグインとしては利用できません。 このような場合には、 プラットフォームチャネル.
プラットフォームチャンネルはダーツ間の双方向通信ブリッジを作成します およびネイティブ コード (iOS では Swift、Android では Kotlin)。あなたは Java を書いているわけではありません o レガシー Objective-C: コルーチンと async/await を備えた最新の Kotlin と Swift それぞれ。このガイドでは、以下にアクセスする完全なプラグインを構築します。 デバイスの近接センサーに接続します。Flutter コアでは利用できません。
何を学ぶか
- MethodChannel: Dart → レスポンス付きネイティブ呼び出し (リクエスト/レスポンス)
- EventChannel: ネイティブ → Dart からのデータの連続ストリーム
- BasicMessageChannel: 非構造化双方向通信
- エラー処理: 両方向からの PlatformException
- スレッディング: メインスレッド、バックグラウンドスレッド、FlutterEngine
- プラグイン パッケージ: 再利用可能なプラグインの構造
- Pigeon: プラットフォーム チャネル向けのタイプ セーフなコード生成
MethodChannel: 同期リクエスト/レスポンス
Il メソッドチャネル そして最も一般的なタイプのチャネル: Dart コード ネイティブ メソッドを呼び出し、応答を待ちます。ワンショット操作に最適 デバイスに関する情報を取得する方法、ネイティブ画面を開く方法、 生体認証操作を実行します。
// 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: 連続データ ストリーム
L'イベントチャンネル データのストリーミング用に設計: センサー 継続的に更新される、地理位置情報イベント、ハードウェア通知。 ネイティブは、ウィジェットがリッスンできるイベントを Dart ストリームに送信します。 ストリームビルダー。
// 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: タイプセーフなコード生成
Pigeon コードを生成するための公式 Flutter ツール 共有 Dart 定義からのプラットフォーム チャネルのボイラープレート。 手動によるシリアル化と保証を管理する必要がなくなりました。 Dart とネイティブ コードが同期していること。
// 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
プラットフォーム チャネルと既存のプラグインを使用する場合
- プラットフォーム チャネルを作成する前に、pub.dev を検索してプラグインがすでに存在するかどうかを確認します。
- pub.dev には 40,000 を超えるパッケージがあります。ネイティブ API のほとんどはすでにカバーされています
- プラットフォーム チャネルの使用目的: エンタープライズ独自の SDK、非常に特殊な API、既存のネイティブ レガシー コードとの統合
- 新しい共有可能なプラグインの場合: 型の安全性とメンテナンスの軽減のために Pigeon を使用してください
結論
プラットフォーム チャネルは、Flutter を真にユニバーサルにするメカニズムです。 任意のネイティブ API であり、MethodChannel または EventChannel でアクセス可能です。ハト 型安全性が追加され、定型文の大部分が削除されます。鍵となるのは 優れたネイティブ統合と適切なエラー処理 PlatformException とリソースのクリーンアップ (リスナーの登録解除、ストリームのキャンセル) 正しいライフサイクルで。







