10
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Organization

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

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で、Webや各種デスクトップOS向けのアプリまで作ることができます。
開発はDartで行います。JavaScriptJavaを混ぜて、一部に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の環境構築については省きます。

導入してみる

  1. Flutterのbranch切り替え
    これから使う機能は実験的な機能であり、betaalphaには取り込まれていない機能です。masterに切り替えましょう。
  2. Flutterモジュールを作る 
    masterブランチに切り替えられていれば、以下のコマンドを使うことができます。
  flutter create -t module {{module_name}}

  これで、モジュールができました。
3. モジュールを導入する  
  各種gradleを編集して作成したモジュールを導入します。まずはsettings.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を呼び出すことができます。こんな感じで使います。

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を入力する度にホットリロードが実行されます。以下のような感じです。 

mov2.gif

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

この段階でも十分に開発効率は向上します。

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します。
続けて、以下のようなシェルスクリプトを作ります。

hot_reload.sh
screen -S {{セッションの名前}} -p 0 -X stuff "r"

ここまでできた段階で、一度dartファイルを変更して、上記スクリプトを実行してみましょう。うまくいけばホットリロードが実行されます。

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

スクリーンショット 2018-12-11 2.41.47.png

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

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

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

mov3.gif

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

おわりに

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

参考


  1. Flutter 1.0: Google のポータブル UI ツールキット 

  2. InstantRunはAndroidStudio2.0の時代から導入され、当時は気まぐれでビルドができなくなったりと不安定でしたが、現在はかなり安定しており、そこまで神経質になる必要もなくなったのかなとも感じています。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
10
Help us understand the problem. What are the problem?