LoginSignup
105
77

More than 3 years have passed since last update.

Flutter MethodChannel APIの使い方

Last updated at Posted at 2020-02-08

はじめに

FlutterはクロスプラットフォームなUIフレームワークです。UI特化のため、UI作る以外のプラットフォーム固有の機能を利用する場合には、公開されているライブラリ (プラグイン) を利用する以外にはFlutterの専用APIを利用してプラットフォーム側の実装を行う必要があります。

プラットフォーム固有機能の例は以下です。

  • Audio/Videoなどのメディアのデコード, エンコード, 再生
  • ランタイムパーミッションの表示や設定確認
  • Bluetoothや加速度センサーなどのハードウェア機能
  • WebView
  • ストレージやファイルアクセス

Dart⇆プラットフォーム双方向でまとまった情報が無かったため、ここにまとめることにしました。
今回はいくつかあるAPIのうち、最もよく使うMethodChannelについて使い方を解説します。

関連記事

Flutter (Dart) とプラットフォーム (Android/iOSなど) 間の通信/呼び出しAPIについてまとめています。

1. Flutter MethodChannelとは

Dartからプラットフォーム (Android/iOS等) のメソッドを呼び出すもしくは、プラットフォームからDartのメソッドを呼び出すためのAPIです。イメージ的には、AndroidのJNI (Java/KotlinからC/C++を呼び出すI/F) に近いですが、違いはとても簡単に呼び出せるのと非同期APIだという点です。

2. MethodChannelの仕組み

Flutterのアーキテクチャを以下に示します。
スクリーンショット 2020-02-16 18.56.00.png

MethodChannel APIの使い方は後述しますが、呼び出し先のメソッド名と引数のデータの2つを引数として渡します。Flutter Framework内ではMethodChannelをBinaryMessagesという形に変換し、Flutter Engineとメッセージパッシングのやり取りを行います。

この界面でAPIコールが非同期のメッセージパッシング (データ送受信) に変わります。

Flutter Engineはデータを受け取ると、そのデータをMethod ChannelのAPIの形に変更し、対象のプラットフォームのAPIをコールします。この仕組みを実現しているのがFlutter Engine内に存在するPlatform Channelsです。
スクリーンショット 2020-02-10 21.29.31.png

3. 基本フロー

実際のコードの説明の前に、APIの基本的なフローについて解説します。ただし、プラットフォーム毎に若干API名が異なる場合があるため (iOS) 、注意してください。

3.1 Dart → プラットフォーム

  • [プラットフォーム側] MethodChannel#setMethodCallHandlerでコールバックを登録
  • [Dart側] MethodChannel#invokeMethodで呼び出したいメソッド名とデータをセットして非同期でコール
  • [プラットフォーム側] 受け取ったデータ (メソッド名) を見て、対象の処理を実施し、Result#success (エラーの場合はerror) をコール
  • [Dart側] メソッドコールの結果を確認

スクリーンショット 2020-02-11 18.20.03.png

3.2 プラットフォーム → Dart

プラットフォーム側からDartを呼び出すことも出来ます。Dart → プラットフォームの場合と手順は同じです。

スクリーンショット 2020-02-11 18.20.33.png

4. サンプルプロジェクト

実際のAPIの使い方は後述しますが、サンプルプロジェクトを以下に用意しています。そのまま動作確認可能ですので、参考にしてください。
ただし、正式に何かの機能を実装する場合は、プラグインの形で組み込んだ方が良いです。
https://github.com/Kurun-pan/flutter-methodchannel-example

5. Flutter + Android (Kotlin) のコード解説

5.1 Dartからプラットフォーム (Kotlin) を呼び出すケース

5.1.1 Dart側の実装

通信チャンネル作成

MethodChannelで通信チャンネルを作成します。引数に指定する文字列は他のアプリケーションと被らず、一意に決まるようにするために慣例で"アプリパッケージ名/チャンネル名"とするのが一般的な様子です。

プラットフォーム側の呼び出し

チャンネル作成後はそのチャンネル#invokeMethodを非同期でコールすることで、Kotlin側を呼び出すことが出来ます。
APIの仕様は引数の仕様は以下のようになっています。

  • 第一引数に呼び出したいメソッド名の文字列を指定
  • 第二引数には呼び出しメソッドの引数に指定するデータを指定。
    • 型はプリミティブ型で使える型に制約あり (StandardMessageCodec)
    • 複数の引数を指定指定したい場合、JSON形式のようにMapで記述するのが便利かと
invokeMethod仕様
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async

サンプルコード

実際のサンプルコードを以下に示します。

main.dart
import 'package:flutter/services.dart';

class _MyHomePageState extends State<MyHomePage> {
  static const MethodChannel _channel = const MethodChannel('com.example.methodchannel/interop');

  static Future<dynamic> get _list async {
    final Map params = <String, dynamic> {
      'name': 'my name is hoge',
      'age': 25,
    };
    final List<dynamic> list = await _channel.invokeMethod('getList', params);
    return list;
  }

  @override
  initState() {
    super.initState();

    // Dart -> Platforms
    _list.then((value) => print(value));
  }

5.1.2 プラットフォーム側の実装

通信チャンネル作成

Dart側と同じように、MethodChannelで通信チャンネルを作成します。引数の文字列はDart側と同じにする必要があります。

メソッド名の取得

チャンネル作成後、MethodCallHanderのコールバックを設定します。コールバックメソッドの引数methodCall.metodに、Dart側のinvokeMethodの第1引数の文字列が格納されているため、それを見て適切な処理を行います。

引数の取得

Dart側のinvokeMethodの第2引数は、methodCall.argumentから取得出来ます。ただし、Dartとプラットフォーム間で受け渡し可能な型には限りがあるため、詳細はこちらを参照して下さい。もし非サポートの型を指定すると、実行時にエラーになります。

サンプルコード

Kotlinコード
class MainActivity: FlutterActivity() {
    companion object {
        private const val CHANNEL = "com.example.methodchannel/interop"
        private const val METHOD_GET_LIST = "getList"
    }

    private lateinit var channel: MethodChannel

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
            if (methodCall.method == METHOD_GET_LIST) {
                val name = methodCall.argument<String>("name").toString()
                val age = methodCall.argument<Int>("age")
                Log.d("Android", "name = ${name}, age = $age")

                val list = listOf("data0", "data1", "data2")
                result.success(list)
            }
            else
                result.notImplemented()
        }
    }

Dart側に結果を返す

プラットフォーム側からDart側に結果を返す場合は、MethodCallHanderのコールバックメソッドの第2引数のMethodChannel.Resultクラスインスタンスをコールすることで実現できます。サンプルコードのようにリターン値を渡すことも可能です。

受け取ったResultインスタンスをローカルに保存し、処理を行った後で結果を返すことも可能ですが、その場合には必ずUIスレッドで結果を返す必要があります。

参考のために以下にResultクラスの各メソッドのAPI仕様を掲載しておきます。

MethodChannel.Result.success仕様
@UiThread
void success(@Nullable Object result)
MethodChannel.Result.error仕様
@UiThread
void error(String errorCode,
                     @Nullable
                     String errorMessage,
                     @Nullable
                     Object errorDetails)
MethodChannel.Result.notImplemented仕様
@UiThread
void notImplemented()

5.2 プラットフォーム (Kotlin) からDartを呼び出す

基本的にDartからプラットフォームを呼び出す方法と同じです。このメソッドの実行は必ずUIスレッドから行ってください。

プラットフォーム側の実装

作成したチャンネルに対して、invokeMethodをコールするだけです。
引数もプリミティブ型で渡すことが可能で、Dart側からの結果もMethodChannel.Resultのコールバックで受け取ることが可能です。不要なら指定なしでOKです。

Kotlinコード
channel.invokeMethod("callMe", listOf("a", "b"), object : MethodChannel.Result {
    override fun success(result: Any?) {
        Log.d("Android", "result = $result")
    }
    override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
        Log.d("Android", "$errorCode, $errorMessage, $errorDetails")
    }
    override fun notImplemented() {
        Log.d("Android", "notImplemented")
    }
})
result.success(null)

Dart側の実装

作成したチャンネル#setMethodCallHandlerでMethodCallを設定します。

メソッド名の取得

コールバック引数のcall.methodでプラットフォーム側から指定された第1引数の値を取得出来ます。

引数の取得

call.argumentsに格納されています。

結果のリターン

成功 (通常) の場合は、Future.valueで結果を返します。
Future.errorを利用すると、プラットフォーム側のMethodChannel.Result.errorがコールされます。
MethodChannel.Result.notImplementedの指定方法は、よく分かっていません。知っている方がいたら教えてください…。

サンプルコード

Dartコード
  Future<dynamic> _platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case 'callMe':
        print('call callMe : arguments = ${call.arguments}');
        return Future.value('called from platform!');
        //return Future.error('error message!!');
      default:
        print('Unknowm method ${call.method}');
        throw MissingPluginException();
        break;
    }
  }

  @override
  initState() {
    super.initState();

    // Platforms -> Dart
    _channel.setMethodCallHandler(_platformCallHandler);
  }

6. 参考文献

105
77
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
77