🏨

NOT A HOTEL Androidの技術構成

2024/07/11に公開

NOT A HOTELは、自宅にも別荘にもホテルにもなる「あたらしい暮らし」をつくろうとしています。
NOT A HOTELのアプリでは、利用予約からチェックイン・チェックアウト、ドアの解錠、スマートホームの操作、困った際のチャットサポートまで、一連のプロセスがアプリ内で完結するようになっています。この記事では、NOT A HOTELのAndroidアプリの技術構成について紹介します。

開発体制

NOT A HOTELは元々iOSアプリが先行しており、AndroidユーザーはWebアプリを利用していました。Androidアプリの開発が始まったのは2023年6月頃で、現在は2名のAndroidエンジニアが開発を行っています。
少人数かつ新規アプリ開発のため、最新の技術を駆使して高速に開発し、新規機能を追加しながら、既存のiOSアプリの機能に追いつくことが求められています。しかし、最初に作った品質が今後数年の開発のパフォーマンスにも影響するため、必要なものはしっかりと作り込みつつ、速度を優先すべき箇所とのバランスを常に模索しています。

ビルド構成と各種バージョン

Kotlinは2.0に移行し、KSPへの移行も完了しています。ライブラリの更新はrenovateを使って監視し、「上げてみて、ダメなら戻せば良い」という精神でやっています。

minSdkVersionはAndroid 9に相当する28に設定しており、OSのリリース日からおおよそ5年間をサポートしています。これはNOT A HOTELのウェブサイトにおけるOSバージョンの分布や、各SDKバージョンにおける機能差などを考慮して決定しました。

Gradleの設定はKotlin DSLで記述し、モジュール間で共有すべき設定はComposite Build及びConvention pluginsを使って共通化しています。ライブラリのバージョン管理にはVersion Catalogを使用しています。

アプリの構成

アプリはSingle activityで、UIは全てJetpack Composeで記述しています。
以前書いた「Material 3 やめました (2) 」という記事にあるとおり、独自のデザインシステムを使用しており、Material 3のコンポーネントは必ず独自の共通コンポーネントでラップして利用しています。

通信にはgRPCを採用しています。スキーマの取り込みには専用のAndroidプロジェクトを用意して、スキーマの変更がある度にこれをライブラリとしてビルドし、GitHub Packagesにアップロードしています。
NOT A HOTEL Androidからは通常のライブラリと同様にVersion Catalogでパッケージ名およびバージョンを指定するだけで利用できます。バージョンについても後方互換性を保証する運用になっているため、適当に上げても問題がありません。
gRPC以外には、外部サービスとの通信にREST API、チャットサービスの一部にWebSocketsを使用しています。

NOT A HOTEL固有の機能としては、スマートホームとの連携があります。連携には直接スマートホームと通信せず、gRPCを利用して一度バックエンドを経由する仕組みになっています。詳しくは誰かがそのうち紹介してくれると思います。

アーキテクチャとデータフロー

Guide to app architectureをベースにしています。

ドメインレイヤは基本的に作らず、ViewModelとRepositoryでデータのやりとりをします。例外的にUseCaseを作成するのはViewModel内のビジネスロジックが複雑な場合や、API呼び出しが複雑な場合です。また、Repositoryの取得系メソッドは必ずFlowを返すようにしています。これにより「先にキャッシュを表示して、最新データが読み込めたら更新する」といったデータ更新にも柔軟に対応できるようになっています。ViewModelは複数のFlowをFlow.combineして一つのUiState(data class)を作り、画面(UI)はそれを監視する仕組みになっています。UIロジックはこのUiStateに集約しており、UiState単体でのテストがしやすい形にしています。画面の量が多い場合はUiStateの中に子のUiStateを作ることもあります。子のUiStateはコンポーネントと1対1の対応になることが多いです。

その他、Composeの設計についてはKotlin Fest 2024で発表した話に大体沿っています。

マルチモジュール

エントリポイントとなる:appがあり、その下に多数の:featureが存在するモジュール構成になっています。featureモジュールは他のfeatureモジュールに依存することができません。featureモジュール間でコードを共有したい場合、共通のcoreモジュールに実装し、各featureモジュールがcoreモジュールに依存する形にします。

featureモジュールは基本的に1画面1モジュールです。あまりに単純な画面だったり、画面Aに対して部分的な出し分けをするA'的な画面の場合は、まとめて1モジュールにすることがあります。現在、featureモジュールの数は50~100程度になっています。

coreモジュールは現在以下の4つのみです。

  • core/data
  • core/datasource
  • core/ui
  • core/domain

ライブラリ

DIにはHiltを使用しています。ComposeではNavigationを使用しています。Navigationにはあまりしっくり来ていませんが、2.8.0からはSafeArgsがサポートされるようなので、今後の進化に期待しています。画像読み込みにはGlide Composeを使用しています。「Coilじゃないの?」という声が聞こえそうですが、Coilでも良いと思います。今のところ画像読み込みは単純な使い方しかしておらず、かつ、画像の読み込み箇所は必ずGlideをラップしたComposableを経由するようになっているため、移行も容易です。これに限らずNOT A HOTEL Androidでは、適当にやっても崩壊しない仕組みにコストをかけ、あとは適当にやるという思想が多くのコードに反映されています。

データ関連では先ほど挙げたようにgRPCをメインに、一部でREST APIに対してRetrofit、WebSocketsに対してKtorを使用しています。裏側ではOkHttpやKotlin Serializationを使っています。他にはDataStore、Room、WorkManager等を使う箇所もありますが、使う頻度は現状少ないです。

CI/CD

ここは開発規模に比例する部分もあり、2人体制の現時点でこう、という話になります。

基本的にGitHub Actionsを使用しており、まず、デイリーでユニットテスト及びLintを実行しています。PR単位で実行しないのは、マージまでの時間を短縮するためです(マージまでのリード時間は数十分程度になっています)。アプリのストアリリースはfastlaneで自動化しており、QAビルドはFirebase App Distributionで配布しています。デプロイのジョブはGitHub上でPRに特定のラベルをつけることで発火するようになっています。

開発環境

マシンのスペックはアプリのビルド時間に大きく影響し、生産性に直結します。
NOT A HOTELでは、必要なスペックのマシンが申請に応じて貸与されるようになっていて、昨年入社した自分はM2 MaxのMacBook Proを使っています。Android開発単体で見るとややオーバースペックな気もしますが、iOSのコードを確認するためにXcodeを同時起動することもあり、結果的に助かっています。

また、近年では生成AIを活用した開発が盛んになっています。
NOT A HOTELでは、GitHub CopilotやChatGPTを会社で契約しているため、個人の負担無しで心置きなく利用することができます。

普段のツールとしてはFigma/GitHub/Notion/Slack/Google Meet等を使って開発しています。

おしまい

NOT A HOTELのAndroidアプリの開発について広く浅く紹介しました。
実装よりな話は書ききれなかったので、反響があればまた何か書くかもしれません。よろしくお願いします。

NOT A HOTEL

Discussion