【Flutter】MethodChannelでネイティブコードを呼び出す📣
背景
Flutterでネイティブアプリ向けSDKを利用してAPI実装が必要だったので、調査した内容をまとめました。
ゴール
ネイティブコードの呼び出しができること。
- iOS:Swift(+α Objective-C)
- Android:Kotlin(+α Java)
SDKを利用できること
- iOS:framework, xcframework
- Android:AAR
検討した方法
MethodChannelを使用する
実装例
ネイティブコードの呼び出し
使用する場面が多そうなSwiftとKotlinの例を用意しました。
Objective-CとJavaについてもメモ書きしたので参考になれば🙏
Objective-C, Java
Objective-CまたはJavaを直接呼び出す
実装の参考は以下
- Objective-C:https://docs.flutter.dev/platform-integration/platform-channels?tab=type-mappings-obj-c-tab#step-4-add-an-ios-platform-specific-implementation
- Java:https://docs.flutter.dev/platform-integration/platform-channels?tab=android-channel-java-tab#step-3-add-an-android-platform-specific-implementation
Swift(またはKotlin)からObjective-C(またはJava)を呼び出し、それをFlutterから呼び出す
- Objective-Cの実装
XCodeのFile>New>File..からObjective-CFile(EmptyFile)
を作成
#import <Foundation/Foundation.h>
+#import "hoge.h"
+
+@implementation Hoge
+
+ // 処理
+-(void) testMethod {
+ NSLog(@"Hello from Objective-C!");
+}
+
+@end
XCodeのFile>New>File..からHeaderFile
を作成
#ifndef hoge_h
#define hoge_h
+#import <Foundation/Foundation.h>
#endif /* hoge_h */
+// クラス定義
+@interface Hoge : NSObject
+// メソッド定義
+-(void) testMethod;
+
+@end
ブリッジファイルにimportを追加
#import "GeneratedPluginRegistrant.h"
+#import "hoge.h"
- Swiftの実装
Objective-Cのメソッドを呼び出し
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
+ let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
+ let methodChannel = FlutterMethodChannel(name: "Channel",binaryMessenger: controller as! FlutterBinaryMessenger)
+ methodChannel.setMethodCallHandler({
+ (call:FlutterMethodCall, result:FlutterResult) -> Void in
+ switch call.method {
+ case "getHello" :
+ Hoge.init().testMethod()
+ result("Hello from Objective-C!")
+ default :
+ result(nil)
+ }
+ })
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
- Flutterの実装
+ import 'package:flutter/services.dart';
//
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
Widget build(BuildContext context) {
+ // ネイティブの処理を呼び出す
+ Future<void> getHello() async {
+ const channel = MethodChannel('Channel');
+ final resultText = await channel.invokeMethod('getHello');
+ debugPrint(resultText);
+ }
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
+ onPressed: () async => getHello(),
child: const Text('getHello'),
)
],
),
),
);
}
}
- テキストボタンを押下するとデバックコンソールに
Hello from Objective-C!
と表示される。
- Swiftの実装
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
+ let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
+ let methodChannel = FlutterMethodChannel(name: "Channel",binaryMessenger: controller as! FlutterBinaryMessenger)
+ methodChannel.setMethodCallHandler({
+ (call:FlutterMethodCall, result:FlutterResult) -> Void in
+ switch call.method {
+ case "getHello" :
+ result("Hello from Swift!")
+ default :
+ result(nil)
+ }
+ })
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
- Kotlinの実装
package com.example.flutter_native_sample
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+ MethodChannel(
+ flutterEngine.dartExecutor.binaryMessenger,
+ "Channel"
+ ).setMethodCallHandler { call, result ->
+ when (call.method) {
+ "getHello" ->
+ result.success("Hello from Kotlin!")
+
+ else ->
+ result.success(null)
+ }
+ }
+
+ }
}
- Flutterの実装
+ import 'package:flutter/services.dart';
//
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
Widget build(BuildContext context) {
+ // ネイティブの処理を呼び出す
+ Future<void> getHello() async {
+ const channel = MethodChannel('Channel');
+ final resultText = await channel.invokeMethod('getHello');
+ debugPrint(resultText);
+ }
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
+ onPressed: () async => getHello(),
child: const Text('getHello'),
)
],
),
),
);
}
}
- テキストボタンを押下するとデバックコンソールに
Hello from Swift!
またはHello from Kotlin!
と表示される。
SDKの利用
- iOSの実装
framework(xcframework)の取り込み
ios
フォルダをXcodeで開き、「Target > Runner > General > Frameworks,Libraries...」にframeworkをドラックアンドドロップ
※xcframeworkの場合でも同様
ブリッジファイルにimportを追加
Objective-Cライブラリの場合のみ必要な手順で、Swiftライブラリの場合は不要かも
#ifndef Generated_Plugin_Registrant_h
#define Generated_Plugin_Registrant_h
#import "GeneratedPluginRegistrant.h"
+// .modulemapの`framework module {モジュール名}`を参照し定義
+#import <{モジュール名}/{モジュール名}.h>
#endif
frameworkの呼び出し
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
+ let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
+ let methodChannel = FlutterMethodChannel(name: "Channel",binaryMessenger: controller as! FlutterBinaryMessenger)
+ methodChannel.setMethodCallHandler({
+ (call:FlutterMethodCall, result:FlutterResult) -> Void in
+ switch call.method {
+ case "hoge" :
+ self.hoge()
+ result("hoge")
+ default :
+ result(nil)
+ }
+ })
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
+ private func hoge() -> Void {
+ // 追加したライブラリのメソッドを呼び出し
+ Hoge.fuga()
+ }
}
- Androidの実装
AARの取り込み
android/app
配下にlibs
フォルダを作成
.aar
を格納し依存関係を記述
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation files('libs/{ファイル名}.aar')
}
allprojects {
repositories {
~
+ flatDir {
+ dirs 'libs'
+ }
}
}
AARの呼び出し
+import {必要なパッケージのインポート(「more actions...」で入れれる)}
~~~
class MainActivity : FlutterActivity() {
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+ MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "Channel").setMethodCallHandler { call, result ->
+ when (call.method) {
+ "hoge" -> {
+ Hoge().huga()
+ result.success("hoge")
+ }
+
+ else ->
+ result.success(null)
+ }
+ }
+
+ }
}
- Flutterの実装
+ import 'package:flutter/services.dart';
//
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
Widget build(BuildContext context) {
+ // ネイティブ(ライブラリ)の処理を呼び出す
+ Future<void> hoge() async {
+ const channel = MethodChannel('Channel');
+ final resultText = await channel.invokeMethod('hoge');
+ debugPrint(resultText);
+ }
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
+ onPressed: () async => hoge(),
child: const Text('hoge'),
)
],
),
),
);
}
}
- 正常にビルド、ライブラリのメソッド呼び出しができればOK!
まとめ
MethodChannelを使うことでネイティブの呼び出しができるのは便利ですが、
要件によっては複雑さが増して対応が難しいケースもあると思うのでその都度ネイティブ知識のキャッチアップが欠かせなそうです。
一例)ネイティブのメソッドの引数にUIViewController型が必要なケース
AppDelegate.swiftでUIViewControllerを継承したクラスを渡しても処理が実行されず。。
対象のメソッドを呼び出すためにSwiftで画面を作成しないといけないかもでハマった。
結論、let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
のcontrollerを渡すことで正常に作動。
参考
Discussion