6 年にわたる Android アプリの開発環境改善への取り組み

N予備校 Android アプリ は 2016 年 4 月にリリースされてから執筆時点(2022 年 8 月)まで、6 年以上に渡って開発・運用されてきました。この 6 年間で Android まわりでは新しい技術が続々と登場し、古い技術が次々と非推奨になっていきました。

この記事では、技術の変化が激しかった 6 年間で、Android チームが開発環境の改善に対してどのように取り組んだのかをまとめます。同じく技術の変化と闘っているみなさんの参考になればと思います。

アーキテクチャを整備する(2017 年 2 月 ~ 2021 年 4 月)

Android アプリをリリースした当初は、MVP をベースに Repository パターンを導入したアーキテクチャでした。アーキテクチャの図は以下のとおりです。

MVP アーキテクチャの図

しかし、こちらのアーキテクチャでは Presenter の役割が大きくなりすぎたため、1,000 行を超える Presenter が生まれていました。また、Fragment と Presenter の境界が曖昧だったため両者が密結合している画面もあり、メンテナンスが難しくなっていました。

それに加えて、Presenter のロジックにはテストがついていなかったり、非同期通信の際にこまめにスレッドを手動で変える必要があったりと、コードを読む・書く難易度が高い状態でした。

これらの課題の解決のため、2017 年にクリーンアーキテクチャを参考にしてレイヤーを追加した設計を導入しました。アーキテクチャの図は以下のようになります。

MVP + クリーンアーキテクチャの図

このアーキテクチャの要点は以下の通りです。

  • クリーンアーキテクチャに沿って、出力に近い部分(Android 特有の部分)とビジネスロジックを明確に分離した
  • Injector を作成して、その画面が関わるクラスの初期化を全て行うことで、依存関係をわかりやすくした
  • UI を操作するコードの量が多かったので、1 画面に対して複数の Presenter を作成して処理を分散させた
  • 非同期の処理を UseCase で実行することで、スレッドを毎回設定する必要がなくなった
  • ロジック、データモデル、API 通信の各処理に対してテストをつけた

このアーキテクチャによって、コードの役割が分離され、UI とロジックを整理できたので、生産性が大きく向上しました。そして 2017 年の終わりには Android Architecture Components がリリースされ、ViewModel が登場したことで Google が MVVM アーキテクチャを推奨するようになります。

それを受けてN予備校のアーキテクチャでも ViewModel を導入し、Fragment や Presenter がばらばらに持っていたデータを ViewModel に保存するようにしました。ViewModel 導入後のアーキテクチャ図は以下の通りです。

MVVM + クリーンアーキテクチャの図

ただ、実際にこのアーキテクチャを全てのコードに適用するのはかなり大変でした。新機能の実装などと並行でコツコツ進めて、約 4 年かけてアーキテクチャを普及させました。

しかし、アーキテクチャが整備されたおかげでコードが見やすくなり、機能の追加や修正を素早く、安全に行えるようになっています。時間をかけてコツコツ取り組んだことで、将来にわたって大きなメリットを受けられるようになったので、費用対効果の高い取り組みだったと思います。

Kotlin を導入する(2017 年 4 月 ~ 2020 年 9 月)

アーキテクチャを整備しはじめたころに、Google I/O 2017 にて Kotlin が Android の正式な開発言語になることが発表されました。また、2 年後の Google I/O 2019 では Kotlin を Android アプリ開発の優先言語とする Kotlin ファーストが発表されました。

2016 年にリリースしたN予備校アプリはもちろん全て Java で記述されていたため、2017 年 4 月から全てのコードを Kotlin に書き換える取り組みを始めました。Android Studio がある程度 Java → Kotlin の自動変換を行ってくれるため、先述のアーキテクチャの整備よりも容易に進めることができました。

Kotlin に書き換えた経緯は以下に詳しく記載していますので、よろしければご覧ください。

blog.nnn.dev

また、Kotlin への移行に伴ってテストライブラリの Mockito が書きづらくなったため、リファクタリングの一環で mockito-kotlin の導入も行いました。参考までに当時の記事を掲載します。

blog.nnn.dev

途中で新機能の実装に集中していた時期もあり、全てのコードを Kotlin に移行するには全体で約 3 年半がかかりましたが、Kotlin の導入によって以下のような非常に大きなメリットが得られました。

  • コードが簡潔に、そして安全に処理が書けるようになり、生産性が大きく向上した
  • 新しい Android のライブラリへの対応が可能になった
  • 採用活動で開発環境を紹介するときの魅力の 1 つになった

そして Kotlin への移行を終えることで、よりモダンな開発環境に移行する準備が整いました。

Jetpack Navigation を導入する(2020 年 1 月 ~ 2021 年 4 月)

Kotlin への移行に終わりが見えてきたころ、次の改善として Jetpack Navigation の導入を行いました。Navigation は 2019 年 3 月にリリースされた画面遷移のライブラリで、これまでの煩雑だった画面遷移の処理を大きく改善してくれるものでした。

N予備校でも Naigation 導入前は画面遷移の処理が非常に複雑で、いくつものファイルを経由してようやく別の画面に遷移できる、という状況でした。そのため不具合の発生リスクが高く、テストがしづらいのでその不具合に気づけない可能性も高かったです。

Navigation を導入にしたことで、それらの課題が大きく改善されました。Navigation を活用することで画面遷移の処理を簡潔に書くことができたため、不具合の発生を防ぐことができ、画面遷移の処理が呼び出されていることを確認するテストを作成することもできました。

さらに Navigation 自体は非常にシンプルで扱いやすく、学習コストが低く抑えられたのも良かったです。また、Navigation で画面遷移を定義すると、漏れなく xml で画面遷移を可視化できるため、自然と画面遷移図が生まれるのも大きなメリットでした。

以下の図は「フォーラム」という生徒や先生が質問や連絡を投稿・回答する機能に Navigation を導入したときに生まれた画面遷移図です。トップ画面から投稿画面、閲覧画面、検索画面にどのように遷移するのかが可視化されています。

Navigation Graph の図

Navigation を導入することで、複雑な画面遷移の処理から解放され、画面遷移図も手に入れることができたので、大きな生産性の向上につながりました。

Single Activity にする(2021 年 7 月 ~ 2021 年 9 月)

Navigation が導入されたことで Fragment 間の画面遷移が簡単になり、そうなると Activity 間の画面遷移の処理が手間になってきました。Google も Navigation を最大限に活用するには Single Activity を推奨していたこともあり、N予備校も従来の Multi Activity 構成から Single Activity への移行を行いました。

Single Activity に移行した詳しい背景と、移行にあたって大変だったこと、Single Activity によって得られたメリットは以下の記事に詳しく記載していますので、よろしければご覧ください。

blog.nnn.dev

Jetpack Compose を導入する(2021 年 8 月 ~ )

Jetpack Compose によって Android アプリの開発にも宣言的 UI が訪れます。Google I/O 2019 で発表された Compose は、そのあと約 2 年かけて開発され、2021 年 7 月に正式にリリースされました。

N予備校でも宣言的 UI を採用するメリットは非常に大きかったため、リリースと同時にチームで学習を始め、1 ヶ月半の学習期間を費やしたあとにアプリに導入していきました。執筆時点(2022 年 8 月)でも Compose 化を進めていますが、主な画面はほぼ全て Compose に移行しており、あとはいくつかのダイアログを残すだけになっています。

Jetpack Compose の学習内容や導入手順、Compose を実装してよかったこととつまづいたことは以下の記事にまとまていますので、詳しくはこちらをご覧ください。

blog.nnn.dev

マルチモジュールを導入する(2022 年 1 月 ~ )

Jetpack Compose の導入を進めていくと、徐々にアプリのビルド時間の長さが課題になってきました。Compose では Kotlin で画面を作成するため、xml とは違い、実装した見た目を確認するにはアプリをビルドする必要がありました。つまり、アプリのビルド時間が長ければ長いほど、Compose による実装効率は下がってしまいます。

そこでビルド時間の改善のためにマルチモジュールを採用することにしました。Android ではコードを変更してからアプリをビルドするとき、変更差分が含まれるモジュールとその関連モジュールだけがビルドされます。そのため、各モジュールが小さく、疎結合になっているほどビルドが速くなります。

もともとのN予備校アプリはシングルモジュール構成だったため、少しのコードの変更でもアプリ全体をビルドする必要があり、ビルドに時間がかかっていました。これをマルチモジュール構成に変更するために、意味のあるまとまりごとにコードを整理していきました。モジュール構成図は以下のようになります。

マルチモジュール構成図

このモジュール構成では 4 つのレイヤー(app, ui, domain, data)を定義しました。レイヤーを分割することで依存関係を制限して、適切なアーキテクチャが保たれるようにします。各レイヤーの内容は以下の通りです。

  • app レイヤーではアプリと各画面の起点(Activity, Fragment, Navigation)を持つ
  • ui レイヤーでは見た目を作成するコード(ViewModel, Presenter, Compose)を持つ
  • domain レイヤーでは非同期処理を伴う複雑なロジック(UseCase)を持つ
  • data レイヤーではアプリが扱うデータ(Repository, Interface, Data Class)を持つ

余談ですが、このレイヤー構成は Android Developers に記載されているアーキテクチャガイドを参考にしています。

また、app レイヤーと ui レイヤーにおいては機能ごとにもモジュールを分割しています。N予備校はホーム、教材(マイコース)、フォーラム、授業、設定という機能があり、これはアプリのメニューの表示に対応しています。

アプリのメニュー

このように機能ごとにモジュールを分割した理由は 2 つあります。

1 つ目は機能ごとの依存を最小限にするためです。以前は教材が授業の処理を参照していて、教材の機能を修正したつもりなのに実は授業の挙動も変わっていた、というようなことがありました。これは重大な不具合につながる恐れがあったため、モジュールを分割することで依存をなくし、機能ごとに独立して修正を可能にしました。

2 つ目はマルチモジュールを導入した理由でもある、アプリのビルドを速くするためです。機能ごとにモジュールを分割することで各モジュールサイズが小さくなり、Compose を修正した後のビルドが速くなりました。そのため Compose を使用した開発が快適になりました。

ただ、執筆時点(2022 年 8 月)ではまだ app レイヤーに UI を操作するコードがいくらか残っており、それらを ui レイヤーの各機能のモジュールに移行を進めています。マルチモジュール化もほとんど全てのコードに触れる必要があるため、なかなか大掛かりな取り組みで、全体で 1 年ほどかかりそうです。

継続的に開発環境を改善するために大事なこと

これまで 6 年にわたって上記のように開発環境の改善に取り組んできましたが、なかなか大変なことも多かったです。その中で、継続的に改善に取り組むために大事だと思ったことは、開発者が代わっても続けていける環境を作ることです。

開発環境を改善する取り組みは、内容によっては数年単位でかかることがあり、その間に新しい人が入ったり、逆に出て行ってしまうこともあるでしょう。その際に取り組みが中断されてしまっては、開発環境が改善されるどころか中途半端な開発環境になってしまい、かえって生産性が低下してしまいます。

開発環境を改善するときには、人が入れ替わっても良いように(特に提案者がいなくなっても良いように)、改善に取り組む背景や理由、具体的な手順をドキュメントに残しておくことが大事です。そうすれば、人が入れ替わっても、途中で新機能実装を優先して改善が中断されたとしても、うまく引き継いで開発環境の改善が完了することでしょう。

また、ドキュメントもアプリのコードも、長い期間使われていくものについては書く時間よりも読まれる時間のほうが圧倒的に長いです。あなたがいなくなっても、あなたのドキュメントやコードは残り続けます。願わくば「このドキュメント・コードを書いた人は素晴らしいな」と思ってもらえるように、普段からドキュメントやコードを書くときは、5 年後にあなたのことを何も知らない人が読んでも理解できるように心がけていきたいですね。

これから

執筆時点(2022 年 8 月)では先述の通り Jetpack Compose の導入とマルチモジュール化に取り組んでいますが、N予備校 Android アプリの開発環境を改善していくためにはまだまだ課題が残っています。改善課題の一部は以下の通りです。

  • Retrofit を導入して通信周りの処理を改善する
  • Dagger Hilt を導入して各クラスのインスタンス化を省略する
  • RxJava を Kotlin Coroutine, Flow に置き換えていく

そして、Android ではこれからもまだまだ新しい技術が登場していくことでしょう。N予備校 Android アプリでは、引き続き開発環境の改善に取り組んでいきますので、もし協力してくださる方がいらっしゃいましたら、ぜひカジュアル面談でお話をさせてください!

カジュアル面談応募フォームはこちら

www.nnn.ed.nico

speakerdeck.com