Flutter MessageChannel APIの使い方

# はじめに

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

* [MethodChannel](https://qiita.com/kurun_pan/items/db6c8fa94bbfb5c0c8d7)
* [EventChannel](https://qiita.com/kurun_pan/items/6d63ebf1e894d3620b20)
* MessageChannel　←　今回

# 1. MessageChannelとは

プラットフォーム (Android/iOSなど) とFlutter (Dart) 間で双方向でメッセージ送受信するためのAPIであるMessageChannel (正確には[BasicMessageChannel](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html)) です。片方からもう片方にデータを送信し、受信したらリプライを返すというごく普通のメッセージパッシング用のAPIです。

Flutterの仕組み的には、[ここ](https://qiita.com/kurun_pan/items/db6c8fa94bbfb5c0c8d7#2-methodchannel%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF)で解説しているPlatform Channelsを利用しています。

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

## 送信側

1. [BasicMessageChannel](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html)でチャンネル生成
2. [BasicMessageChannel](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html)#[send](https://api.flutter.dev/flutter/services/BasicMessageChannel/send.html)でデータ送信

## 受信側

1. [BasicMessageChannel](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.html)#[MessageHandler](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html)でイベントリスナーを登録
2. データ受信時に登録したイベントリスナーの[BasicMessageChannel](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.html)#[MessageHandler](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html)#[onMessage](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html#onMessage-T-io.flutter.plugin.common.BasicMessageChannel.Reply-)がコールバックされるため、その中でリプライデータを送信

## Dart → プラットフォームへの送信
<img width="1014" alt="スクリーンショット 2020-02-09 19.48.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/75d727c9-d80f-08bc-3d88-abbd9068fb24.png">

## プラットフォーム → Dartへの送信
<img width="1014" alt="スクリーンショット 2020-02-09 19.48.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259963/2c0d5d44-3e8b-7be1-6adc-d9223f9f9923.png">

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

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

## 4.1 Dart側の実装

### 通信チャンネル作成
MethodChannel同様、[BasicMessageChannel](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html)の通信チャンネルを作成します。

```Dart:API仕様
BasicMessageChannel(String name, MessageCodec<T> codec, {BinaryMessenger binaryMessenger})
```

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

第2引数は送信データのコーデックタイプを指定します。Frameworkの内部的にはバイナリデータとして扱われるため、その情報を指定する必要があります。

|タイプ|内容|
|---|---|
|BinaryCodec|ByteDataのデータ送信時|
|JSONMessageCodec|JSON形式のデータ送信時|
|StandardMessageCodec|通常はこちらを利用(プリミティブ型)|
|StringCodec|Stringデータを送信時|

なお、サンプルプロジェクト↓ではJSONMessageCodecを利用したデータ送信の例をコメントアウトした形で残していますので、参考にしてください。
https://github.com/Kurun-pan/flutter-messagechannel-example/blob/master/lib/main.dart

### データ送信
[BasicMessageChannel](https://api.flutter.dev/flutter/services/BasicMessageChannel-class.html)#[send](https://api.flutter.dev/flutter/services/BasicMessageChannel/send.html)でデータ送信

```Dart:API仕様
Future<T> send(T message) async {
  return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));
}
```

### サンプルコード

サンプルコードは、Dart側からプラットフォーム側に文字列 (String型) を送信＆リプライ受信と、プラットフォーム側からのメッセージ受信＆リプライ送信の双方向の内容を盛り込んでいます。

```Dart:Dartサンプルコード
class _MyHomePageState extends State<MyHomePage> {
  static const _channel = BasicMessageChannel('com.example.messagechannel/interop', StringCodec());

  String _platformMessage;

  void _sendMessage() async {
    final String reply = await _channel.send('Hello World form Dart');
    print(reply);
  }

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

    // Receive messages from platform
    _channel.setMessageHandler((String message) async {
      print('Received message = $message');
      setState(() => _platformMessage = message);
      return 'Reply from Dart';
    });

    // Send message to platform
    _sendMessage();
  }
```

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

### 通信チャンネル作成
[BasicMessageChannel](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.html)の通信チャンネルを作成します。

```Kotlin:API仕様
BasicMessageChannel(BinaryMessenger messenger, String name, MessageCodec<T> codec)
```

第3引数にはString型のデータ送信時にはStringCodec.INSTANCEを指定します。StringCodecのstaticインスタンスになり、[ソースコード](https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/plugin/common/StringCodec.java)を見てもらった方が早いです。

### 受信待機

[BasicMessageChannel](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.html)#[setMessageHandler](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html)でイベントリスナーを登録します。

### リプライ送信

データ受信時に登録したイベントリスナーの[BasicMessageChannel](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.html)#[MessageHandler](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html)#[onMessage](https://api.flutter.dev/javadoc/io/flutter/plugin/common/BasicMessageChannel.MessageHandler.html#onMessage-T-io.flutter.plugin.common.BasicMessageChannel.Reply-)がコールバックされるため、その中でリプライデータを送信します。

### サンプルコード

サンプルコードは、プラットフォーム側からプラットフォーム側に文字列 (String型) を送信＆リプライ受信と、Dart側からのメッセージ受信＆リプライ送信の双方向の内容を盛り込んでいます。

```Kotlin:Kotlinサンプルコード
class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        val channel = BasicMessageChannel(
                flutterEngine.dartExecutor.binaryMessenger,
                "com.example.messagechannel/interop",
                StringCodec.INSTANCE)

        // Receive messages from Dart
        channel.setMessageHandler { message, reply ->
            Log.d("Android", "Received message = $message")
            reply.reply("Reply from Android")
        }

        // Send message to Dart
        Handler().postDelayed({
            channel.send("Hello World from Android") { reply ->
                Log.d("Android", "$reply")
            }
        }, 500)
    }
}
```