Android / iOS アプリの開発にクロスプラットフォームの Flutter を実戦投入した

Flutter を実際にリリースしているプロダクトに採用してみて半年ほど経ったので、その経緯と Flutter についての感想を記しておきます。

The English version is on Medium!

Flutter について

Flutter は、クロスプラットフォームでモバイルアプリを開発するための開発フレームワークです。

特徴

- 言語は Dart
- フルスタックのUI framework (Material and iOS)
    - Reactive (inspired by React)
    - Material and iOS
    - GPU を利用して UI を描画するところまで全て (Skia)
- オープンソース on GitHub
    - developed by Google and community
- ネイティブのARMコードにコンパイル
- 開発用プラグイン
    - IntelliJ, Visual Code Studio, Android Studio もサポート
    - IntelliJ で IDEデバッグ
- ホットリロード
- ベータ版 (2018/02/28現在)

技術的な詳細については、本記事の趣旨ではないので省略します。また別途、書きたいと思います!

参考リンク

- 公式サイト flutter.io
- Announcing Flutter beta 1: Build beautiful native apps
- FlutterでDroidKaigi 2018のiOSアプリを作りました
- FlutterでAndroid/iOS両対応のアプリ開発 Google Developers Group DevFest 2017 Tokyo
- Dart言語によるモバイルアプリ(iOS/Android)開発フレームワークFlutterがベータを開始

また、Android をお使いの方は Flutter 公式のサンプルアプリ をインストールして触ってみると、用意されている UI がなんとなくつかめると思います。

アプリについて

CARTUNEというクルマ好きのためのアプリを作りました ( Android / iOS )。以下、いくつかスクリーンショットです。

赤いツールバーなのが Android 版のデザイン。下タブなのは iOS 版のデザイン。ただしスクリーンショットは両方共 Android 実機で撮影しています(フラグで切り替えているだけ)。

参考リンク

- 「MixChannel」生みの親の新たな挑戦は“車”のコミュニティ「CARTUNE」、1.1億円の調達も | TechCrunch Japan

検討および採用の経緯

アプリを開発するにあたり、素早い開発のために、Android / iOS に両対応したクロスプラットフォームの開発SDKを使うことにしました。Android / iOS のどちらも開発経験がありますが、やはり少人数で両 OS 向けに別々に開発するのは大変です。

そこで、Flutter / React Native / Xamarin を検討しました。最終的に Flutter を採用しましたが、以下のようにいくつかのポイントがありました。

言語(Dart)

Flutter は Dart を使ってアプリを書きます。第一印象は Java っぽいと感じました。静的な型付けですし、async / await があって非同期処理が書きやすそうだと思いました。

個人的には JavaScript はちょっと書きたくないな……と思っていたので、Dart は好印象でした。

なお Xamarin で使われている C# もいい言語だと思います。

パフォーマンス

パフォーマンスに関しては、どの SDK も問題になることはないかな、という印象です。

Flutter は Android / iOS ともネイティブの ARM コードにコンパイルされます。また、公式が提供している Flutter Gallary を触ってみて、パフォーマンスに問題がなさそうだという感触を得ました。

React Native は UI はネイティブですが、JavaScript が動作する部分があるのが少し気になりました。ただ、世の中のアプリを触った限りではパフォーマンスは気にならなかったので、多くの場合では問題にはならないとも思いました。

Xamarin は iOS はネイティブにコンパイルですが、Android は Mono VM が動作するそうです(ここがネイティブ・コンパイルだったら、もう少し詳しく検討しましたが……)。

Reactive

Flutter は React からインスパイアされていて Reactive フレームワークです。以前から UI の開発がしやすそうだと Reactive Native が気になっていましたし、Flutter も同様に良さそうだと思いました。

Built-in Widgets

Flutter は多種多様な UI Widgets をフレームワーク自身が持っています。

React Native と Xamarin はネイティブの UI を使う方式(ですよね?)。

このため Flutter では各 OS 間の差異が UI に関しては無く(意図して Android / iOS で挙動や見た目を変えている箇所はあります)プラットフォームごとの違いに苦悩することが減りそうだという期待がありました。

エコシステム

検討していた頃(2017年の夏)は Flutter のライブラリ等はかなり少なく、必要に応じて自分で実装する必要がある状況でした。ただ、Flutter の Widgets は非常に充実しており、UI の実装においてはそれらを組み合わせればよく、特に困らないだろうと思われました(現時点では、その通りの状況です)。

React Native は利用している開発者も多いですし、ライブラリも多いのはとても魅力的でした。

Xamarin は、かなり昔に小さなアプリを試しに作った時に NuGet で苦戦した記憶がありますが、詳しくはわかりません。

ネイティブ連携

どうしても Flutter では実現できない機能が出てきた時に、ネイティブ側のコードで解決できるかどうかを調査しました。

Flutter ではプラットフォーム固有の機能(カメラや位置情報など)はプラットフォーム側の機能を呼び出す必要があります。その場合はプラットフォーム・ネイティブ(Android なら Java、iOS なら ObjC / Swift)で実装することになります。この場合はプラットフォーム標準の開発と同様ですから、世にあるライブラリも使いたいものを使うことができます。

また Flutter 自体がプラットフォーム・ネイティブの世界では全画面表示になっているひとつの View です(この View を通じて GPU で Flutter UI が描画される)。ハイブリッドっぽく、一つの画面に Flutter と プラットフォーム・ネイティブの UI を混在させることが出来ることを、試しに実装して確認しました。この混在させる方法は使わないほうがよく、プロダクトでは使ってはいませんが、出来るかどうかだけは確認しました。

いざとなったら、このプラグイン機構でプラットフォーム側の実装をRPC的に呼び出せば、なんとでも実装できるという最終手段があるのは重要なポイントでした。

感想

UI building

Flutter は Dart のコードで Widgets tree を作ることで UI を定義します。これは、とても快適です。コードなので、条件に応じて UI を切り替えるといったことも簡単です。

UI は Flutter が完全にコントロールしているので、OS ごとの差異で苦労することもありませんでした。一部の UI は Android / iOS で違うデフォルト値を持っていて(例えばツールバーの中央寄せ(iOS)、左寄せ(Android)や、画面遷移のトランジションなど)、それぞれのプラットフォーム標準の見た目や動作に近くなるようになっていますが、プロパティを明示すれば統一することができます。

Flutter が提供している Widget も豊富で、それらの組み合わせで UI が作れるので、サードパーティの UI ライブラリ的なものは必要ありませんでした。

ホットリロード

公式で謳われている「高速なホットリロード」は、本当に誇張なしに1秒以下でリロードできます。

アニGIFを埋め込めなかったのでこちらから別で開いて御覧ください

これは小さなサンプルだけでなく、ある程度の規模の実際のプロダクトであっても、1秒以下でリロードされます。レイアウト部分だけでなく、ロジック部分もリロードできます。しかも、状態を保ったまま。場合によってはフルリロードが必要になり、状態はクリアされますが、そのフルリロードでも2秒程度しかかかりません。

レイアウトやスタイルを実機で見ながら、少し調整してリロード……というのが手軽なので、デザイン調整もしやすいですし、少しずつ小さな部分から UI を作っていくことができます。最初の頃は Widgets tree から完成が想像しにくかったので、リロードを繰り返しながら作りました。

開発効率

アプリ次第で大きく変わるところではありますが、50 - 80% くらいは共通で(つまり Dartで)書けると思います。

CARTUNE では、画像編集の機能をプラットフォーム側で実装しています(画像選択が Android が Intent、iOS が Photos なのと、OpenCVでナンバープレート検出している)。他には Twitter などの SDK 連携もプラグイン機構でプラットフォーム側で実装しています。それ以外の部分は Dart で Android / iOS ともに共有しています。CARTUNE だと画像編集の実装が大きいのもあり、共有できているDart 部分は 70% ほどです。

UI もロジックもシングルコードベースというのは大きな利点だと思います。

これは個々人のスキルセットに依存するのであまり参考にはならないのですが……両 OS を別々で開発するコストが 100 (Android 35、iOS 65)とすると、Flutter であれば 30 - 50 くらいのコストで開発できるのではないかという印象です(あくまで私の開発経験に基づく、個人的な感想です)。

課題

まだ発展途上なので、不具合はあります。GitHub に登録されている Issues も、記事執筆時点で 2,000 以上あります。

もちろん、開発はアクティブなので、どんどん不具合は修正されたり機能が充実したりしているので、今後はより良くなっていくことは期待できます。自分自身も、いくつかバグに遭遇して、Pull Request を送ったりもしました。

とはいえっても、まだベータ版ですし、一定以上の規模であれば、何らかのバグに遭遇したりすると思います。安定性という点では、まだプラットフォーム・ネイティブでの開発には及ばないと思います(他のクロスプラットフォーム SDK との比較では、どうなのか分かりません)。

情報の少なさも課題です。公式サイトやドキュメントは充実していますし、公式サンプルアプリも良い実装例です。そうはいっても、まだまだ足りない部分はあります。

私の場合は、困ったときは Flutter 内部のコードを読みながら、アプリを開発していました。Flutter の UI framework 部分は Dart で書かれていて、アプリ開発と同じ言語ということもあり読みやすく、Widget の使い方がわからなかったり、挙動で疑問があるときは、Flutter 内部を読んでいます。

あとは、StackOverflow では Googler から回答がもらえたりしますし、私もときどき質問を見ています。Google Groups や Gitter もありますが、そちらはあまり見ていません(流れていってしまう系の情報はあまり追わないので)。

向き不向き

UI 構築が大きい割り合いを占めるアプリは、Flutter の恩恵を大きく受けられます。デバイス寄りの機能が大きな割り合いを占めるもの(例えばカメラなど)は、恩恵が小さくなります。

既存のアプリにハイブリッド的に組み込むのは、向いていません。

Flutter ではプラットフォーム・ネイティブの画面へ遷移することはできます。しかし基本的には Flutter の画面をメインとしつつ、寿命の短いフロー(カメラ起動して写真を撮って戻るとか、WebViewでお知らせ画面を表示するとか)でワンポイントでプラットフォーム・ネイティブの画面や機能を使うのがよいパターンだと思います。

また、ゲーム向けではありません(そもそもゲームエンジンではない)。

以下のような場合では、Flutter と相性がよさそうです。

アプリのプロトタイプ
- UI をデザインして、細かい挙動や見た目を調整するイテレーションがやりやすい(ホットリロード)
- Widgets tree の概念が HTML DOM とも似ているので、デザイナーが参加しやすそう

新規アプリの開発
- 最初から Flutter で書ける(既存のアプリに部分的に組み込むのはあわない)
- Flutter のバグを踏んだ時に、回避するワークアラウンドを採用できる(仕様を変えやすい)
    - 以前は動画サポートが無かったので(いまはあります)、CARTUNE ではプラットフォーム・ネイティブの画面へ遷移して動画を再生
    - テキスト入力欄の改行入力の動作が怪しいので、同様にプラットフォーム・ネイティブの画面へ遷移してテキスト入力

UI building が中心的なアプリ
- ニュース系アプリやTwitterアプリのようなもの

まとめ

Flutter を利用して Android / iOS の両方に対応したアプリを素早く開発することができました。開発効率は高くて満足しています。安定性が課題ですが、開発もアクティブなので改善に期待!(もちろん私も contribute していきます)

この記事が気に入ったらサポートをしてみませんか?