概要
Flutterアプリを作成している際、ネイティブコードを使いたい時があります。
ここではiOSでの実装方法を2パターンご紹介します。
外部パッケージの導入は保守や脆弱性対応の対象も増えます。
実装量もそれほど多くないため、選択肢として把握しておくと便利です。
パターン1 Flutterからネイティブコードを呼ぶ
iOS側
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
...
let methodChannel = FlutterMethodChannel(name: "jp.co.jwork.sample.app/sample", binaryMessenger: engineBridge.applicationRegistrar.messenger())
methodChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
let method = call.method
if (method == "input") {
let args = call.arguments as! [Any]
// ...iOSでやりたい処理 ...
result("ok")
} else {
result(FlutterMethodNotImplemented)
}
})
}
}
Flutter側
import 'dart:async';
import "package:flutter/services.dart";
class Caller {
static const MethodChannel _methodChannel = const MethodChannel('jp.co.jwork.sample.app/sample');
Future run(String param) async {
try {
return await _methodChannel.invokeMethod("input", [ param ]);
} on PlatformException {
// エラーハンドリング
}
}
}
パターン2 iOSからイベントをFlutter側に流す
iOSからFlutterへ渡す場合、EventChannelを使用できます。
Flutter側で入り口(EventChannel)を名前付きで用意しておき、名前を元にiOS側で呼ぶイメージです。
なお、1回きりで良い場合は、同じ流れでパターン1のMethodChannelを使うことでも実現可能です。
iOS側
import Flutter
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
static var eventSink: FlutterEventSink?
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
...
FlutterEventChannel(name: "jp.co.jwork.sample.app/sampleEvent", binaryMessenger: engineBridge.applicationRegistrar.messenger())
.setStreamHandler(self)
}
// 何らかのiOS側処理の値
private func onInput(_ value: String) {
AppDelegate.eventSink?(value)
}
}
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
AppDelegate.eventSink = events
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
AppDelegate.eventSink = nil
return nil
}
}
Flutter側
import 'dart:async';
import "package:flutter/services.dart";
class Receiver {
static const EventChannel _eventChannel = const EventChannel("jp.co.jwork.sample.app/sampleEvent");
StreamSubscription? _listenEventStreamSubs;
// 引数で関数を受けていますが、単純にStreamSubscriptionを返す作りでも良いと思います。
Future<void> listen(void Function(String value) onListen) async {
await _listenEventStreamSubs?.cancel();
_listenEventStreamSubs = null;
_listenEventStreamSubs = _eventChannel.receiveBroadcastStream().listen((event) {
if (event is String) {
onListen(event);
}
}, onError: (error) {
// エラーハンドリング
}, cancelOnError: true);
}
// 不要になったら閉じます。
Future<void> cancel() async {
await _listenEventStreamSubs?.cancel();
_listenEventStreamSubs = null;
}
}
まとめ
MethodChannel、EventChannelで簡単にiOSコードとやりとりが出来ました。
汎用的なものならpluginとして切り出し、使いまわせるようにするのも良いと思います。