Text To Speech for Flutter

[Flutter](https://flutter.io/)で、Text To Speechをするために Pluginを作成したお話。

---

## What is Flutter

- マテリアルデザインアプリを簡単に作成できるSDK
- AndroidとiOSの両方でビルド
- Googleによって開発されたオープンソースのツール
- 開発ツール(CLI)、使いやすい言語(Dart)、豊富なウィジェット(UI)、優れたIDEサポート(IntelliJ)を提供


---
## Flutter Widget Framework

- UIはWidgetで構築。Reactからインスピレーションを得たリアクションスタイルのフレームワーク
- Dart VM 上で実行
- cf.[A Tour of the Flutter Widget Framework \- Flutter](https://flutter.io/widgets-intro/)

---

## Using Packages & Developing Plugins

- すでに多くのパッケージが公開されている。httpやデバイス向けAPI(バッテリー)など
- cf.[Dart Packages](https://pub.dartlang.org/)
- 自らパッケージを開発することもできる
- cf.[Developing Packages & Plugins](https://flutter.io/developing-packages/)

---

## Custom platform-specific code with platform channels

- プラットフォームのコードを実行したい場合は、プラットフォームチャネルを使用する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/12177/a346e8d5-8856-82de-dfd6-49f3f91bff09.png)

- 図のように、プラットフォームチャネルを使用してクライアントとプラットフォーム間でメッセージのやり取りを行う。

- ｃｆ．[Writing custom platform\-specific code with platform channels \- Flutter](https://flutter.io/platform-channels/)

---

## Plugin　at Flutter

- 任意の名前を指定して、MethodChannelを作成し、invokeMethodを定義する。
- invokeMethodには、プラットフォーム側に渡すメッセージと引数を定義。

```dart
class TtsPlugin {
  // ttsのチャンネルを定義
  static const MethodChannel _channel = const MethodChannel('net.granoeste.flutter.plugin/tts_plugin');

　　　　// speechメソッドが呼ばれたら、invokeMethodでプラットフォームへメッセージ通知
  static Future speech(String text) => _channel.invokeMethod("speech", text);

  static Future stop() => _channel.invokeMethod("stop");

  static Future language(String language) => _channel.invokeMethod("language", language);

}
```
- cf.[MethodChannel](https://docs.flutter.io/javadoc/io/flutter/plugin/common/MethodChannel.html)
 
---

## Implement Platform Code (Android)

*プラグインの登録*

```java
public class TtsPlugin implements MethodCallHandler {

    /** Plugin registration. */
    public static void registerWith(Registrar registrar) {
        final MethodChannel methodChannel = new MethodChannel(registrar.messenger(), "net.granoeste.flutter.plugin/tts_plugin");

        TtsPlugin ttsPlugin = new TtsPlugin(registrar.activity(), methodChannel);

        methodChannel.setMethodCallHandler(ttsPlugin);
    }

    private TtsPlugin(Activity activity, MethodChannel channel) {
        this.activity = activity;
    }

```

*メッセージ受け取り時の実装*

```java
    @Override
    public void onMethodCall(MethodCall call, Result result) {
        if (call.method.equals("speech")) {
            String speechText = call.arguments.toString();
            // TODO: speech
            result.success(true);
        } else if (call.method.equals("stop")) {
            // TODO: stopSpeech
            result.success(true);
        } else if (call.method.equals("language")) {
            // TODO: set language
        } else {
            result.notImplemented();
        }
    }

```

---

## App use plugin

基本的には、Pluginのメソッドを呼び出し、メッセージをプラットフォームへ伝えるだけ。

```dart

  Future _textToSpeech() async {
    final res = await TtsPlugin.speech("Hello world");
  }

  Future _language() async {
    final res = await TtsPlugin.language("en");
  }
```

プラグインの登録自体は、自動生成されたJavaのクラスのGeneratedPluginRegistrantで行ってくれます。

```Java
/**
 * Generated file. Do not edit.
 */
public final class GeneratedPluginRegistrant {
  public static void registerWith(PluginRegistry registry) {
    TtsPlugin.registerWith(registry.registrarFor("net.granoeste.ttsplugin.TtsPlugin"));
  }
}
```

---

## Summary
Text To Speech をFlutterでやりたかったので実装してみましたが、MethodChannelを使用してプラットフォームにメッセージの送信してネイティブのコードを実行する実装だけ行い、ネイティブコードとFlutterとの連携なぎ込みなどはツールがよしなにやってくれたので割りと簡単に作成することができました。

作成したPluginはGitHubに公開中
[granoeste/tts_plugin at develop](https://github.com/granoeste/tts_plugin/tree/develop)