3.9K Views
April 25, 24
スライド概要
Flutterから ネイティブコードに アクセスする 2024.4.25 関西モバイルアプリ研究会A #3 itok@そらかぜ
itok@そらかぜ • いとうけい(itok) • の中の人 • モバイルアプリエンジニア 兼 CEO • 副業でフリーランス的 • https://itok.jp/, https://sorakaze.co.jp/, @itokjp
所在地:京都市中京区 社員1人=自分 2015年創業9期目
実績 自社 受託 公私合わせて200件ほどのアプリ開発に携わる
iOS / Android / macOS / Windows / サーバ 一人でやってます(デザイン以外)
Flutterから ネイティブコードに アクセスする
pub.dev
ここを作る話
どんなシーン? • ネイティブに実装されたOSの新機能をいち早く使 いたい時 • Flutterサポートのないライブラリを使いたい時 • • 広告SDKとか (Flutterへの移行途中とか?)
非UIでの処理
MethodChannel MessageChannel
MethodChannel • ネイティブのメソッドを呼び出すイメージ • ネイティブからは非同期で返す
MessageChannel • Flutter - ネイティブの双方向通信 • どちらからもchannel越しにメッセージを送れる
MethodChannel Battery Levelを取得するサンプル
Flutter側
チャンネル作成
static const channel = MethodChannel("jp.itok.kanmoba_a3/battery");
Future<int> _getBatteryLevel() async {
メソッド名指定で実行
int level = await channel.invokeMethod<int>("getBatteryLevel");
...
}
iOS側
// @objc class AppDelegate: FlutterAppDelegate
// didFinishLaunchingWithOptions内
let controller = window?.rootViewController as! FlutterViewController
チャンネル作成
let batteryChannel = FlutterMethodChannel(name: "jp.itok.kanmoba_a3/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler {
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
メソッド名指定で受け取って処理
if call.method == "getBatteryLevel" {
let level = self.getBatteryLevel()
result(level)
} else {
result(FlutterMethodNotImplemented)
}
}
)
ちなみに
Future<int> _getBatteryLevel() async {
int level;
...
}
private fun getBatteryLevel(): Int {
var level: Int
...
}
private func getBatteryLevel() -> Int {
var level: Int
...
}
Flutter
Dart
Future<int> _getBatteryLevel() async {
int level;
...
}
Android
Kotlin
private fun getBatteryLevel(): Int {
var level: Int
...
}
iOS
Swift
private func getBatteryLevel() -> Int {
var level: Int
...
}
😵💫
ネイティブUI
Flutter UiKitView AndroidView PlatformViewLink PlatformViewFactory ネイティブ PlatformView
Flutter側 UiKitView( viewType: "jp.itok.kanmoba_a3/nativeView", 引数 creationParams: creationParams, 引数のエンコード/デコード方式 creationParamsCodec: const StandardMessageCodec(), )
iOS側: PlatformView
class NativeView: NSObject, FlutterPlatformView {
private var _view: UIView
init(frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?) {
_view = UIView()
super.init()
createNativeView(frame: frame,
viewIdentifier: viewId,
arguments: args)
}
ここで実際のViewを返す
func view() -> UIView {
_view
}
}
iOS側: PlatformViewFactory class NativeViewFactory: NSObject, FlutterPlatformViewFactory { func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { Viewを生成 return NativeView(frame: frame, viewIdentifier: viewId, arguments: args) } 引数のエンコード/デコード方式 func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return FlutterStandardMessageCodec() } }
iOS側: 登録 // @objc class AppDelegate: FlutterAppDelegate // didFinishLaunchingWithOptions内 Factoryを登録 let factory = NativeViewFactory(messenger: registrar!.messenger()) registrar(forPlugin: "pluginName")! .register(factory, withId: "jp.itok.kanmoba_a3/nativeView")
既存のネイティブプロジェクト ViewControllerまるごと Flutterで使えるのでは?
雑感 • UIViewの配下という位置づけなので UIViewControllerをchildViewControllerとしては 扱ってくれない • SwiftUIもUIHostingController経由で使えるが・・・
まとめ • 3プラットフォームを行き来するので一人で全部や るとちょっと大変 • 簡単なものならpub.devに頼るよりメンテが楽な場 合もあるかも • ネイティブプロジェクトからの移行としては微妙