Flutter 製アプリで Android ・ iOS のライブラリを使用する

こんにちは、iOS チームの池沢と申します。

最近「鎌倉殿の 13 人」を見返していますが、やはり最高の大河ドラマだと思います。

上総広常の最後のシーンは、今後も何度も見返すんだろうなと思います。

大河ドラマの話はさておき、今回は私が先日まで行っていた Flutter での機能開発の話を書こうと思います。

他のエンジニアからも時々聞かれる話ですので、イメージしやすい話になっていれば幸いです。

背景

ジモティーではゴミの削減やリユースの促進を目的に、自治体と連携しながらリアル店舗(ジモティースポット)の運営を行っております。

先日この取り組みをさらに加速させていくため、初の大型店舗を神奈川県川崎市にオープンしました。

ジモティーでは一般に公開しているアプリの他に、この店舗内で使われる業務用アプリの開発も行っております。

開発は Flutter で行っており、リユース品の管理やジモティーへの投稿など業務効率化のためのさまざまな機能があります。

課題

背景で説明したように、業務効率化のため業務用アプリにもさまざまな機能開発を行っています。

その一つとして、先日ジモティースポットにとあるシステム(例えば、コンビニやスーパーにあるセルフレジのようなものをイメージしていただけたらと思います)を導入しようという動きがありました。

しかし、そのシステムをアプリから使えるようにするために調査していたところ、 Flutter 用のライブラリは用意されておらず Android ・ iOS それぞれにライブラリが用意されておりました。

実装

Flutter には Android ・ iOS のそれぞれのネイティブコードを呼び出すための仕組みがあります。

今回はそれを用いることで、ライブラリが Flutter 向けでなくてもそのライブラリを使えるようにしました。

Method Channel とは

Flutter は、プラットフォーム固有の API をそれらと連携する言語で呼び出すことができます。

その仕組みの中で最も有名だと思うのが Method Channel と呼ばれる方法です。

以下で、この Method Channel を用いた具体的な実装方法を、 Android を例にとって記します。

Flutter 側

はじめに、チャンネルを作成します。

final platform = MethodChannel('jmty.flutter.dev/samples');

その後、 invokeMethod によりメソッドを呼び出します。ジェネリクスの型は、返り値の型を指定します(指定しないと dynamic 型になります)。

final result = await platform.invokeMethod<String>('cash_register')

以上で呼び出しは完了です。

Android 側で呼び出したメソッドの処理が完了すると、その返り値が result に入ります。

Android 側

Flutter プロジェクトには android フォルダがあり、その中に Android 用アプリのコードが格納されています。

その中の MainActivity.kt は FlutterActivity を継承しており configureFlutterEngine メソッドが定義されていますが、 MethodChannel を使用するとこの中に処理が入ります。

class MainActivity: FlutterActivity() {
  private val CHANNEL = "jmty.flutter.dev/samples"

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      // 下で追記
    }
  }
}

この中で、呼んだメソッド毎に処理を実装します。

正常終了すれば success とともに返り値を渡し、正常終了できなければ error と共にエラーコードやエラーメッセージを渡します。

メソッドが定義されてなければ、 notImplemented を呼びます。

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
  call, result ->
  when (call.method) {
    "cash_register" -> {
      val methodResult = sampleMethod() // 処理の実装
      if (methodResult) {
        result.success("success") // 正常終了
      } else {
        result.error("failure", "error message", null) // 異常終了
      }
    }
    else -> {
      result.notImplemented()
    }
  }
}

これで実装は終了です。

正常終了した場合、 dart の result には success が入ります。

異常終了した場合は PlatformException が throw されます。

まとめ

Flutter で Android ・ iOS のライブラリを使う手順を記しました。

Flutter はやはり開発体験が良く柔軟性も高いので、開発していて楽しいですね。

KMP も stable 版がリリースされ、今後はよりクロスプラットフォーム開発が広まっていくことを期待しています。

最後に

ジモティーでは、一緒に課題を解決してくれるエンジニアを募集中です!大きな成長を遂げたいという強い想いをお持ちの方にジョインしていただけることを待ち望んでおります! https://jmty.co.jp/recruit_top/