既存AndroidアプリにFlutterを導入して12.5倍速で開発する

Retty Advent Calendar 2018 16日目の記事です。
昨日は @tkngue さんの記事で[ベンチャー企業におけるDWH DevOps @ Retty](https://engineer.retty.me/entry/2018/12/15/120322_dwh)でした。

本記事はRettyでAndroidアプリをメインに開発している @shyne がお送りします。

# はじめに
　最近、`Flutter 1.0`がリリースされたことが話題になっています。 [^1]
個人的にFlutterに触る機会の多い私としましても非常に喜ばしいことで、HummingBirdなどにはとても期待しています。
現在RettyのAndroidアプリは部分的にリニューアルを行っていますが、その過程で、「Flutterを導入してはどうか」、という意見があり(~~実は私のゴリ押しな部分も~~)、実際に導入を検討し、試験的に入れてみたことがありました。
本記事では、そこで得た既存アプリにFlutterを後から導入する方法を共有したいと思います。

# Flutterってなに？

<img src="https://qiita-image-store.s3.amazonaws.com/0/105287/10416333-56c0-de1c-2925-c96bdbf2c4cb.png" width=25%>
　Flutterとは、Google製のポータブルUIツールキットです。ReactNativeのように、Android/iOS両方をサポートできるクロスプラットフォームな開発ツールです。
先ほど言及したHummingBirdや[flutter-desktop-embedding](https://github.com/google/flutter-desktop-embedding)で、Webや各種デスクトップOS向けのアプリまで作ることができます。
開発はDartで行います。<font color="Gold">JavaScript</font>と<font color="Crimson">Java</font>を混ぜて、一部に<font color="Blue">Kotlin</font>のエッセンスを注入したような言語です。この言語の__VMでの実行もネイティブでの実行もサポートされる__という特徴を生かし、開発中はVMで実行することでホットリロードをサポートし、高速な開発を行うことができます。リリースビルド時にネイティブコードにコンパイルされるので、プロダクションでは快適に動作します。

# なぜFlutter？
　RettyのiOSアプリでは以前から[ReactNativeを活用](https://qiita.com/PONTA-N/items/807f00f515e7b4084721)しています。その高い生産性により、高速でPDCAを回すことが可能となっていました。Androidでも同じようなより良い開発を行える環境を作りたいと考えていた所に、Flutterが登場しました。Fltuterは以下のような開発体験を素晴らしいものにするいくつかの要素があります。

- 🔥__ホットリロード__🔥をサポートしている
    - デザイナーさんとUIの仕上げを行う際に、微調整の度にビルドでいちいち待つ必要がありません。<font color="Red">__最高です__</font>💪
    - AndroidのInstantRunよりずっと速いですし、ずっと安定しています[^2]

- 🤖__ネイティブコード__🤖にコンパイルできる
    - ReactNativeを始めとして、クロスプラットフォームな開発ツールはWebViewや各種JavaScriptエンジンで実行されることが多く、パフォーマンスについて不安な点が残りますが、Flutterであればそこまでパフォーマンスについてシビアになる必要はないです

- ✍__マテリアルデザイン__✍を使いやすい
  - Flutterは外部のライブラリを導入しなくても、標準で使えるUIコンポーネントが非常に充実しています。[カタログ](https://flutter.io/docs/development/ui/widgets/material)からその豊富さが伺い知れます。なんならネイティブで使えるAndroidXがサポートしていないようなUIコンポーネントもFlutterであればサポートしていることもあります。これによって、苦労せずにいい感じのUIをサクッと作ることが可能となっています。

これらにより、Flutterを導入することでアプリ開発の体験は一段階良いものになるでしょう。

# Flutterを導入しよう！
　Flutterには開発中かつ実験的ですが、既存のアプリに導入するためのモジュールを作成する機能が存在します。これを活用して、RettyのAndroidアプリにFlutterを導入してみます。なお、Flutterの環境構築については省きます。

## 導入してみる
1. Flutterのbranch切り替え  
  これから使う機能は実験的な機能であり、`beta`や`alpha`には取り込まれていない機能です。`master`に切り替えましょう。  
2. Flutterモジュールを作る　  
  `master`ブランチに切り替えられていれば、以下のコマンドを使うことができます。  

  ```bash
  flutter create -t module {{module_name}}
  ```

　　これで、モジュールができました。
3. モジュールを導入する　　
　　各種gradleを編集して作成したモジュールを導入します。まずは`settings.gradle`です。以下の内容を追記してください。   

  ```gradle:settings.gradle
  setBinding(new Binding([gradle: this]))
  evaluate(new File(
  '{{作成したモジュールのパス}}/.android/include_flutter.groovy'
  ))
  ```
　　
　　次に`app/build.gradle`です。dependenciesに追記を行います。

  ```app/build.gradle
  dependencies {
      implementation project(':flutter')
  }
  ```
以上で導入が完了しました！簡単ですね！ここまでできれば既存アプリのコード上からFlutterの画面を呼び出すことができます。

## 使ってみる
`Flutter.createView()`でFlutterのViewを呼び出すことができます。こんな感じで使います。

```Kotlin
val flutterView = Flutter.createView(
        this,
        lifecycle,
        "route1"
)
```
第一引数に`Activity`、第二引数に`Lifecycle`、第三引数にFlutterで表示したい画面のパスを文字列で渡します。nullを渡すと`/`が自動で使われるようです。ここで渡したパスは、Flutter側では`window.defaultRouteName`で取得することができ、以下のようなコードでパスに応じたウィジェットの出し分けが可能になります。

```dart
void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case 'route1':
      return SomeWidget(...);
    case 'route2':
      return SomeOtherWidget(...);
    default:
      return Center(
        child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
      );
  }
}
```

`Flutter.createFragment()`というメソッドもあり、こちらはFragmentとしてFlutterのViewを得ることができます。

```Kotlin
val flutterFragment = Flutter.createFragment(
        "route1"
)
```
第一引数に表示したい画面のパスを文字列で渡します。

FragmentやView単位など、かなり柔軟に扱うことができるので、画面単位の導入ではなく、コンポーネント単位での導入をして手軽に試してみることができそうです！　

## ホットリロードしてみる

ホットリロードを行うには、`flutter attach`コマンドを利用します。コマンド実行後、`r`を入力する度にホットリロードが実行されます。以下のような感じです。　

![mov2.gif](https://qiita-image-store.s3.amazonaws.com/0/105287/368519dc-964b-2b4b-d6b2-ef8a6a353be3.gif)

ソースコードの変更内容が一瞬で反映されました！素晴らしいです！　　

この段階でも十分に開発効率は向上します。  
Kotlin/JavaのコードとDartのコードの規模が全く違うので、あまり正確な比較はできませんが、具体的な数値を上げてみると、  

- InstantRunでKotlin/Javaを再ビルド : 早いときで約10秒
- Flutterでホットリロード : __約0.8秒__

と、ビルド時間が<font color="Red">__12.5倍__</font>の速さになっていました！Kotlin/Javaのコードはある程度差分が大きくなるとInstantRunでもビルド時間が1分以上かかってしまうこともあり、それを考慮するとまさに__爆速__と言っても過言ではないでしょう！

が、後もうひと押しな部分があります。　　

## 真の🔥ホットリロード🔥をしてみる
　やはり、ホットリロードを行いたいときに`r`を入力しないといけないのが面倒ですね。本来のFlutterやReactNative、webpackのようなビルドツールは変更後即ホットリロードが実行され、このような余計なことをしなくても良いのです。では、面倒と感じたことは自動化してみましょう。

まず、`screen`コマンドで適当なセッションを作り`flutter attach`してみます。

```bash
screen -S {{セッションの名前}}
flutter attach
```
`attach`に成功した段階でctrl+a, dを入力してセッションからdetachします。
続けて、以下のようなシェルスクリプトを作ります。

```bash:hot_reload.sh
screen -S {{セッションの名前}} -p 0 -X stuff "r"
```
ここまでできた段階で、一度dartファイルを変更して、上記スクリプトを実行してみましょう。うまくいけばホットリロードが実行されます。

　最後にAndroidStudioにファイルの変更にフックしてのタスク実行を実現するプラグイン、[file-watchers](https://plugins.jetbrains.com/plugin/7177-file-watchers)を導入して仕上げに移ります。
プラグイン導入後、PreferencesのTools以下に`File Watchers`という項目が追加されているので、これを選択します。最初は何もない状態なので、下の「＋」ボタンを押して新しいタスクを作ります。以下の画像のように各項目を設定します。

<img width="746" alt="スクリーンショット 2018-12-11 2.41.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/105287/5d9ee72d-541e-7466-5cd4-94b99eed9054.png">

設定の必要な各項目の意味については以下のようになっています。　

- Name　　
    - タスクの名前です。後から識別しやすいものにしましょう。
- File type　　
    - 監視したいファイルのタイプです。今回はFlutterに関わるDartファイルを監視したいので、Dartとしておきます。
- Scope　　
    - 監視したいファイルの範囲です。編集するDartファイルが含まれる部分に設定しましょう。
- Program  
    - ファイルの変更を検知したら実行するものです。今回は先ほど作った`hot_reload.sh`を実行するようにします。

設定を終えたら実際に動かしてみましょう！Dartファイルを編集し、保存すると、以下のようにいちいちターミナルで`r`を入力しなくても勝手にリロードしてくれるようになります！

![mov3.gif](https://qiita-image-store.s3.amazonaws.com/0/105287/44514c0d-11d3-3da4-1cf3-0834c821a957.gif)

最高ですね！これで爆速でアプリ開発ができそうです！

# おわりに
　アプリ開発において、長い長いビルド時間は解決の難しいどうしようもない問題で、多くのアプリエンジニアの時間を奪い続けてきましたが、そんな時代もそろそろ終わりを迎えそうです。既存のアプリにFlutterを導入する試みも、まだ実験段階であり、プロダクションでの利用は戸惑われる部分もありますが、非常に簡単に行うことができました。ぜひFlutterを盛り上げて、退屈なアプリ開発をよりエキサイティングに楽しいものにしていきましょう！！

# 参考
- [Add Flutter to existing apps](https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps)


[^1]: [Flutter 1.0: Google のポータブル UI ツールキット](https://developers-jp.googleblog.com/2018/12/flutter-10-googles-portable-ui-toolkit.html)
[^2]: InstantRunはAndroidStudio2.0の時代から導入され、当時は気まぐれでビルドができなくなったりと不安定でしたが、現在はかなり安定しており、そこまで神経質になる必要もなくなったのかなとも感じています。