• トップ
  • ブログ一覧
  • Flutterでネイティブコードを呼び出してみよう
  • Flutterでネイティブコードを呼び出してみよう

    すどたく(エンジニア)すどたく(エンジニア)
    2023.06.05

    IT技術

    iOS、Androidの両方に対応できて便利なFlutterですが、Flutterの仕様、OSの仕様でさまざまな問題が起こることがあります。

    そんな時に役立つ解決策の一つがネイティブコードを呼び出しiOS、Androidそれぞれで解決してしまう作戦です。この記事ではコードの呼び出し方を実際の使用例を交えて解説します。

    Flutter側での実装

    Flutter側はMethodChannelクラスを使用して呼び出しを行います。

    1  Future<String> _getHelloWorld() async {
    2    const MethodChannel channel = MethodChannel("Channel");
    3    String? resultText = await channel.invokeMethod("getHelloWorld");
    4    return resultText ?? "null";
    5  }

    MethodChannelのインスタンスを作成し、invokeMethod処理を呼び出します。それぞれの引数としてString型を要求されます。後ほどネイティブコード側での識別に使うため分かりやすい値を入れておくといいでしょう。

    invokeMethodの返却値はここではStringを指定しています。ネイティブコード側で返却できるのはStringの他Boolean、Int、Doubleなど特定の型に限られています。

    また、List型を返すinvokeListMethod、Map型を返すinvokeMapMethodも用意されています。必要に応じて使いましょう。

    Swift側の実装

    Flutterプロジェクト内の「ios/Runner」フォルダ内、「AppDelegate.swift」ファイルを編集します。Xcodeで行うとやりやすいと思います。

    1    override func application(
    2        _ application: UIApplication,
    3        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    4    ) -> Bool {
    5        GeneratedPluginRegistrant.register(with: self)
    6        
    7        let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
    8        let methodChannel = FlutterMethodChannel(name: "Channel",binaryMessenger: controller as! FlutterBinaryMessenger)
    9        methodChannel.setMethodCallHandler({
    10            (call:FlutterMethodCall, result:FlutterResult) -> Void in
    11            switch call.method {
    12            case "getHelloWorld" :
    13                result("Hello from Swift!")
    14            default :
    15                result(nil)
    16            }
    17          })
    18        
    19        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    20    }

    FlutterMethodChannelには先ほどFlutter側で定義したチャンネル名(ここでは"Channel")を指定します。

    また、setMethodCallHandlerのcall.methodにはFlutter側のinvokeMethodに指定した値(ここでは"getHelloWorld")が入っています。ここでは複数のメソッドを呼び出すことを想定しswitch文で分岐しています。

    Flutter側に値を返すにはresult()を使用します。ここでは"Hello from Swift!"の文字列を返しています。先述の通り返却できる型に制限があるので気をつけましょう。

    iOS Simuratorで起動すると、Swiftで実装したコードが呼び出されているのがわかります。

    Kotlin側の実装

    Android側での呼び出しを行うには、「MainActivity.kt」を編集します。

    1class MainActivity: FlutterActivity() {
    2    @OptIn(ExperimentalCoroutinesApi::class)
    3    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    4        super.configureFlutterEngine(flutterEngine)
    5        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "Channel").setMethodCallHandler { call, result ->
    6            when (call.method) {
    7                "getHelloWorld" ->
    8                    result.success("Hello from Kotlin!")
    9                else ->
    10                    result.success(null)
    11            }
    12        }
    13
    14    }
    15}

    Swiftと同じくチャンネル名、invokeMethodの値を指定しresultを使用して値を返却します。Kotlinの場合はsuccsess,errorを指定しますがそれ以外はSwiftと同一です。ちなみに、Swiftでエラーを返したい場合はresult()の引数でFlutterError型を使用します。

    Android Emuratorで起動すると、Kotlinで実装したコードが呼び出されているのがわかります。

    使用例:端末内ストレージのネイティブ呼び出し

    ネイティブコードの使用例を紹介します。

    Flutterで値を保存したい場合、SharedPreferencesを使うことがよくあります。このクラスを使用すると指定したキーに紐付くデータの読み書きができますが、実はこのキー、内部で自動的に「flutter.」の接頭辞が付けられています。普通に使う分には何の問題もない仕様ですが、例えばネイティブアプリからFlutterへ移行する際に、ネイティブアプリで既に保存している内容をFlutter側で読み取れない、という問題があります。

    この画像はFlutter、ネイティブコードでそれぞれキー名「key」で値を保存した際のUserDefaultです。Flutterでは保存時に「flutter.key」という形式で保存されています。読み込み時も同じく「flutter.key」をキー名として探すためネイティブコードで保存した「key」の値が読み込めなくなります。

    そこでネイティブコードを使用し、保存した値の読み出しを行います。まずはSwift側です。

    1        methodChannel.setMethodCallHandler({
    2            (call:FlutterMethodCall, result:FlutterResult) -> Void in
    3            switch call.method {
    4            case "getHelloWorld" :
    5                result("Hello from Swift!")
    6            case "getPreference":
    7                let value = UserDefaults.standard.string(forKey: call.arguments as! String)
    8                result(value)
    9            default :
    10                result(nil)
    11            }
    12          })

    先ほどのコードにgetPreferenceが呼ばれた際の処理を追加しました。call.argumentsにはFlutter側で呼び出し時に渡した値が格納されています。

    1MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "Channel").setMethodCallHandler { call, result ->
    2            when (call.method) {
    3                "getHelloWorld" ->
    4                    result.success("Hello from Kotlin!")
    5                "getPreference" -> {
    6                    val value = PreferenceManager.getDefaultSharedPreferences(context).getString(call.arguments as String, null)
    7                    result.success(value)
    8                }
    9                else ->
    10                    result.success(null)
    11            }
    12        }

    Kotlin側も同様にgetPreferenceが呼ばれた際の処理を追加します。

    1  Future<String> _getPreference(String key) async {
    2    const MethodChannel channel = MethodChannel("Channel");
    3    String? resultText = await channel.invokeMethod("getPreference", key);
    4    return resultText ?? "null";
    5  }

    Flutter側の呼び出し処理は先ほどのgetHelloWorldとほぼ同じです。invokeMethodの第二引数に指定した値がネイティブアプリ側のcall.argumentsで取得できます。

    これで、ネイティブアプリで指定したキーを読み込むことができました。

    まとめ

    ネイティブコードを呼び出すことができれば問題解決の選択肢が増えます。

    クロスプラットフォームだからと食わず嫌いせずに、ぜひネイティブコードを利用してみましょう・

    すどたく(エンジニア)

    すどたく(エンジニア)

    おすすめ記事