Retty Advent Calendar 2018 16日目の記事です。
昨日は @tkngue さんの記事でベンチャー企業におけるDWH DevOps @ Rettyでした。
本記事はRettyでAndroidアプリをメインに開発している @shyne がお送りします。
はじめに
最近、Flutter 1.0
がリリースされたことが話題になっています。 1
個人的にFlutterに触る機会の多い私としましても非常に喜ばしいことで、HummingBirdなどにはとても期待しています。
現在RettyのAndroidアプリは部分的にリニューアルを行っていますが、その過程で、「Flutterを導入してはどうか」、という意見があり(実は私のゴリ押しな部分も)、実際に導入を検討し、試験的に入れてみたことがありました。
本記事では、そこで得た既存アプリにFlutterを後から導入する方法を共有したいと思います。
Flutterってなに?
Flutterとは、Google製のポータブルUIツールキットです。ReactNativeのように、Android/iOS両方をサポートできるクロスプラットフォームな開発ツールです。 先ほど言及したHummingBirdや[flutter-desktop-embedding](https://github.com/google/flutter-desktop-embedding)で、Webや各種デスクトップOS向けのアプリまで作ることができます。 開発はDartで行います。JavaScriptとJavaを混ぜて、一部にKotlinのエッセンスを注入したような言語です。この言語の__VMでの実行もネイティブでの実行もサポートされる__という特徴を生かし、開発中はVMで実行することでホットリロードをサポートし、高速な開発を行うことができます。リリースビルド時にネイティブコードにコンパイルされるので、プロダクションでは快適に動作します。なぜFlutter?
RettyのiOSアプリでは以前からReactNativeを活用しています。その高い生産性により、高速でPDCAを回すことが可能となっていました。Androidでも同じようなより良い開発を行える環境を作りたいと考えていた所に、Flutterが登場しました。Fltuterは以下のような開発体験を素晴らしいものにするいくつかの要素があります。
-
🔥__ホットリロード__🔥をサポートしている
- デザイナーさんとUIの仕上げを行う際に、微調整の度にビルドでいちいち待つ必要がありません。最高です💪
- AndroidのInstantRunよりずっと速いですし、ずっと安定しています2
-
🤖__ネイティブコード__🤖にコンパイルできる
- ReactNativeを始めとして、クロスプラットフォームな開発ツールはWebViewや各種JavaScriptエンジンで実行されることが多く、パフォーマンスについて不安な点が残りますが、Flutterであればそこまでパフォーマンスについてシビアになる必要はないです
-
✍__マテリアルデザイン__✍を使いやすい
- Flutterは外部のライブラリを導入しなくても、標準で使えるUIコンポーネントが非常に充実しています。カタログからその豊富さが伺い知れます。なんならネイティブで使えるAndroidXがサポートしていないようなUIコンポーネントもFlutterであればサポートしていることもあります。これによって、苦労せずにいい感じのUIをサクッと作ることが可能となっています。
これらにより、Flutterを導入することでアプリ開発の体験は一段階良いものになるでしょう。
Flutterを導入しよう!
Flutterには開発中かつ実験的ですが、既存のアプリに導入するためのモジュールを作成する機能が存在します。これを活用して、RettyのAndroidアプリにFlutterを導入してみます。なお、Flutterの環境構築については省きます。
導入してみる
- Flutterのbranch切り替え
これから使う機能は実験的な機能であり、beta
やalpha
には取り込まれていない機能です。master
に切り替えましょう。 - Flutterモジュールを作る
master
ブランチに切り替えられていれば、以下のコマンドを使うことができます。
flutter create -t module {{module_name}}
これで、モジュールができました。
3. モジュールを導入する
各種gradleを編集して作成したモジュールを導入します。まずはsettings.gradle
です。以下の内容を追記してください。
setBinding(new Binding([gradle: this]))
evaluate(new File(
'{{作成したモジュールのパス}}/.android/include_flutter.groovy'
))
次にapp/build.gradle
です。dependenciesに追記を行います。
dependencies {
implementation project(':flutter')
}
以上で導入が完了しました!簡単ですね!ここまでできれば既存アプリのコード上からFlutterの画面を呼び出すことができます。
使ってみる
Flutter.createView()
でFlutterのViewを呼び出すことができます。こんな感じで使います。
val flutterView = Flutter.createView(
this,
lifecycle,
"route1"
)
第一引数にActivity
、第二引数にLifecycle
、第三引数にFlutterで表示したい画面のパスを文字列で渡します。nullを渡すと/
が自動で使われるようです。ここで渡したパスは、Flutter側ではwindow.defaultRouteName
で取得することができ、以下のようなコードでパスに応じたウィジェットの出し分けが可能になります。
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を得ることができます。
val flutterFragment = Flutter.createFragment(
"route1"
)
第一引数に表示したい画面のパスを文字列で渡します。
FragmentやView単位など、かなり柔軟に扱うことができるので、画面単位の導入ではなく、コンポーネント単位での導入をして手軽に試してみることができそうです!
ホットリロードしてみる
ホットリロードを行うには、flutter attach
コマンドを利用します。コマンド実行後、r
を入力する度にホットリロードが実行されます。以下のような感じです。
ソースコードの変更内容が一瞬で反映されました!素晴らしいです!
この段階でも十分に開発効率は向上します。
Kotlin/JavaのコードとDartのコードの規模が全く違うので、あまり正確な比較はできませんが、具体的な数値を上げてみると、
- InstantRunでKotlin/Javaを再ビルド : 早いときで約10秒
- Flutterでホットリロード : 約0.8秒
と、ビルド時間が12.5倍の速さになっていました!Kotlin/Javaのコードはある程度差分が大きくなるとInstantRunでもビルド時間が1分以上かかってしまうこともあり、それを考慮するとまさに__爆速__と言っても過言ではないでしょう!
が、後もうひと押しな部分があります。
真の🔥ホットリロード🔥をしてみる
やはり、ホットリロードを行いたいときにr
を入力しないといけないのが面倒ですね。本来のFlutterやReactNative、webpackのようなビルドツールは変更後即ホットリロードが実行され、このような余計なことをしなくても良いのです。では、面倒と感じたことは自動化してみましょう。
まず、screen
コマンドで適当なセッションを作りflutter attach
してみます。
screen -S {{セッションの名前}}
flutter attach
attach
に成功した段階でctrl+a, dを入力してセッションからdetachします。
続けて、以下のようなシェルスクリプトを作ります。
screen -S {{セッションの名前}} -p 0 -X stuff "r"
ここまでできた段階で、一度dartファイルを変更して、上記スクリプトを実行してみましょう。うまくいけばホットリロードが実行されます。
最後にAndroidStudioにファイルの変更にフックしてのタスク実行を実現するプラグイン、file-watchersを導入して仕上げに移ります。
プラグイン導入後、PreferencesのTools以下にFile Watchers
という項目が追加されているので、これを選択します。最初は何もない状態なので、下の「+」ボタンを押して新しいタスクを作ります。以下の画像のように各項目を設定します。
設定の必要な各項目の意味については以下のようになっています。
- Name
- タスクの名前です。後から識別しやすいものにしましょう。
- File type
- 監視したいファイルのタイプです。今回はFlutterに関わるDartファイルを監視したいので、Dartとしておきます。
- Scope
- 監視したいファイルの範囲です。編集するDartファイルが含まれる部分に設定しましょう。
- Program
- ファイルの変更を検知したら実行するものです。今回は先ほど作った
hot_reload.sh
を実行するようにします。
- ファイルの変更を検知したら実行するものです。今回は先ほど作った
設定を終えたら実際に動かしてみましょう!Dartファイルを編集し、保存すると、以下のようにいちいちターミナルでr
を入力しなくても勝手にリロードしてくれるようになります!
最高ですね!これで爆速でアプリ開発ができそうです!
おわりに
アプリ開発において、長い長いビルド時間は解決の難しいどうしようもない問題で、多くのアプリエンジニアの時間を奪い続けてきましたが、そんな時代もそろそろ終わりを迎えそうです。既存のアプリにFlutterを導入する試みも、まだ実験段階であり、プロダクションでの利用は戸惑われる部分もありますが、非常に簡単に行うことができました。ぜひFlutterを盛り上げて、退屈なアプリ開発をよりエキサイティングに楽しいものにしていきましょう!!
参考
-
InstantRunはAndroidStudio2.0の時代から導入され、当時は気まぐれでビルドができなくなったりと不安定でしたが、現在はかなり安定しており、そこまで神経質になる必要もなくなったのかなとも感じています。 ↩