新規アプリをリリースしました
APP/UX チームの玉置(@tamappe)です。
最近、弊社で新規アプリをゼロからフルスクラッチしてリリースしました。
リリースしたアプリは PROGOS といいます。PROGOS は英語のスピーキング力をAIを使って測定できるアプリです。
PROGOSのサービス自体はWebで先行してリリースされています。今回リリースしたのはそのアプリ版になります。
採用した技術はFlutterです。
プライベートではFlutterを触っていましたが普段の業務でFlutterを使っていないのでまさか業務でFlutterを触り始めるとは思ってもいませんでした。
Twitter アカウント
この記事では全くのゼロからの新規のアプリ開発の場合、どの技術スタックを採用するのがベストプラクティスなのかについて話します。
あるいは技術選定にめちゃくちゃ苦労した話になります。
ちなみにアプリに限らずシステム開発というのは時代やその時のトレンドによってペストプラクティスが変わりますので 2022 年における話になります。
Agenda
- 新規事業アプリのフルスクラッチでの技術選定
- 設計方針と開発の進め方
- チーム開発
1. 新規事業アプリのフルスクラッチでの技術選定
PROGOS のアプリ化について、最初にジャブを受けたのは確か部長の羽田(@jumboornot)との1on1だった記憶があります。
「今から新規でアプリを作るなら何で作るのが一番いい?」
という質問を受けました。
この問いに回答するのは今のアプリ開発界隈では非常に難しいように感じました。
アプリ開発ということで iOS だけではないことは当然です。ここでは iOS アプリと Android アプリの両方を指すことにします。
一番いいのは iOS アプリ、 Android アプリそれぞれネイティブ実装するのが一番いいのかなと。
クロスプラットフォームを使わない場合
今日でのアプリ開発では、iOS は UIKit と SwiftUI で2パターン、 Android では一般的なフレームワークと Jetpack Compose で2パターンのフレームワークが存在します。
Apple は UIKit ではなく SwiftUI を採用することを推していますので、iOSはSwiftUIを採用するのかな。(個人的には Objective-C が好きなので ObjC で iOS アプリを書きたいですね)
SwiftUI は Apple が 2019 年頃にリリースした iOS のネイティブアプリを宣言的 UI で書けるフレームワークです。
私が宣言的 UI とは何かについて語るのはまだ百億年早いと思っていますので省略します。こちらのページが一番わかりやすいです。
iOS ネイティブでゼロから開発するのならおそらく SwiftUI になるだろうことはほぼ確定です。
つまり、設計方針として宣言的 UI を採用することになります。
Android ネイティブで同じように宣言的 UI を採用する場合、これは自動的に Jetpack Compose を採用することになります。
とすると、それぞれ次のように選択することになります。
iOS | Android |
---|---|
SwiftUI | Jetpack Compose |
iOS では(技術選定当時) iOS 15から async/await
が使えるようになりました。今では iOS 13 から使えるみたいです。これから新規アプリを開発する場合はasync/await
を活用したいです。
また、サポートバージョンについてはなるべく古いバージョンはメンテナンスコストを考えると避けたいので最新バージョンがいいです。そのため、iOS のサポートバージョンを iOS 15 以上にしたいです(願望)。
ということでネイティブで開発するなら次のように提案する気がします。
iOS: SwiftUI + async/await (iOS 15 以上) + SwiftPM
あとは仕様の要件に満たすライブラリを頑張ってチョイスしてなんとかします。(だいたいのプロジェクトなんてそんなもん)
同じ発想を Android に適用させると
Android: Jetpack Compose + Kotlin (Android version 6 以上)
ぐらいのことを提案する気がします。あとは宣言的 UI とライブラリの力を存分に利用します。
スマホアプリをネイティブを新規でフルスクラッチする場合はこれぐらいのことまで考える必要があります。
クロスプラットフォームも考慮に入れる場合
次にクロスプラットフォームの Flutter を加えて吟味するとどうなるかについて検証していきます。
Flutter を採用すると最初から宣言的 UI になっています。
Flutter を採用すると標準で async/await を使えます。(これであとに色々ハマったことはまた別のストーリーとしてあります)
Flutter を採用すると一つのソースコードで iOS と Android アプリをリリースできます。
Flutter を採用するとry
ただし、 Flutter を採用すると課題になることがありました。
状態管理手法でどの設計パターンを採用すれば良いのかという重い課題が出ました。
今日の Flutter のパッケージとして、次の3つのパッケージがあります。
- Riverpod
- Provider
- BLoC (Business Logic Component)
つまり、この3つの中からチームメンバーの人数とスキルセットを考慮してパッケージを選定する必要がありました。
これが非常に重かったです。
この課題は次の項目で述べます。
2. 設計方針と開発の進め方
弊社は検証の末、 Flutter を採用することに決めました。
今まで Flutter を使ったアプリをリリースしていないので今回は新しいことの挑戦になります。
弊社の行動指針に Rarejob Way というのがあります。
- やりたいことをやろう (High Alignment High Autonomy)
- ストーリーを語ろう (Share the Whole Story)
- 変化を生み出そう (Make a Difference)
Flutter を採用すると、このうち「やりたいことをやろう」と「変化を生み出そう」は当てはまる気がしております。
アプリ開発で Flutter を採用することは初めてです。そこで、設計方針を吟味しないといけません。
APP / UX チームでアプリを開発できるのは私含めて3人です。
それぞれのメンバーとスキルセットは次の通りでした。
- 部長 (iOS, Android, Flutter (BLoC, Provider経験者, Riverpod未経験))
- Androidアプリエンジニア (Androidネイティブ、Flutter未経験)
- iOSアプリエンジニア (私)
このメンバーとスキルセットを元に3つの設計方針から一つを決定する必要がありました。
色々調査と議論の末にここは次のように決まりました。
Riverpod + MVVM
後述しますが、 freezed のパッケージは採用しませんでした。
3. チーム開発
採用する設計方針を決めたらやっと開発開始です。
全くのゼロからプロジェクトを立ち上げるから始めるのは仕事では初めてです。
アプリ開発において事前に決める項目は限られています。
ポイントは上記だと思っています。
今回全てが初めてでしたので私で全部考えました。考えた後にチームメンバーに私の考えを共有して粉砕されたりしました。
採用したフォルダ構成は大まかには次のようにしました。
フォルダ構成
root/ ├ assets/ │ └ images/ │ └ fonts/ │ └ html/ ├ android/ ├ build/ ├ ios/ ├ lib/ │ └ config/ │ │ └ route_generator.dart │ └ constant/ │ │ └ api_path.dart │ │ └ app_constant.dart │ └ l10n/ │ └ model/ │ └ service/ │ └ ui/ │ │ └ common/ │ │ └ component/ │ │ └ page/ │ │ └ util/ │ └ util/ │ └ view_model/ └ test/
決まったフォルダ構成はもちろん Github の README に記載してメンバーに共有と。
ただし、これは開発当初のフォルダ構成です。リリースする頃には api_path.dart
や app_constant.dart
が無くなるといった変更があります。
モデルとして MVVM を採用していますので
- model/
- ui/
- view_model/
が分かる構成にしました。
ちなみに MVVM の大まな流れを参考にしたのはこちらの記事です。
非常に勉強になりました。
コーディングスタイルと使うWidget
こちらは雰囲気です。
Wikiのページは時間や知見不足で作成できませんでした。
画面に該当するクラスの Widget については iOS ネイティブをモデルにしています。
- 画面に該当するクラスの大元の部分はStatefulWidgetを使う
- Viewに該当するクラスの部分はStatelessWidgetを使う
慣れ親しんでいる UIKit の場合、画面に該当するのは UIViewController
で View に該当するのは UIView
です。
UIViewController
では重要なライフサイクルという概念がありますのでイメージ的にはStatefulWidget
UIView
にもライフサイクルは存在しますが静的なイメージが合うのでStatelessWidget
こんな感じで「ネイティブ開発だったらどうか」を基準にしてWidgetを選定しました。
かなり困ったことが画面に該当するクラスの置き場所を page
にするか screen
にするか。
こちらはFlutterの初期生成プロジェクトで命名されている Widget 名が MyHomePage
でしたので xxxPage(xxx_page.dart)
と命名することに決めました。
APIクライアントを何にするか
Flutter での API 連携は初めての取り組みでしたので、どう実装しようか非常に困りました。
前述のフォルダ構成の serivce
と model
に該当する部分の設計です。
model
部分の実装において、当時 freezed
パッケージが流行っていて設計として採用しても良いと思っていました。
ですが、検討した結果 freezed
の採用は見送りました。使い方を調査をしていた時に使ってみるまでの難しさを体験して挫折しました。
実際に導入することは時間さえ掛ければなんとかなるのですが、今回のアプリ開発ではチーム開発なので他のメンバーも使えるように導入したパッケージの使い方を Wiki にまとめる必要がありました。
freezed
を触ってみて手順書が作れそうにありませんでしたので、導入を断念しました。
確かに便利かもしれません。
ですが、自分でチームメンバーに説明できないぐらいの難しさを感じたら導入しないというのも一つの技術選定だと思っています。
結果的に model
部分は Dart 公式のドキュメントと同じ構成になりました。
API クライアントは http
パッケージを採用しています。
そのため、特にイカした設計にはなっておらず標準的なスタイルに落ち着いたかなと思っております。
終わりに
非常に長々と解説しましたが、無事に Flutter 製の PROGOS アプリを世に出すことができて満足しています。
よければ、使ってやってください!