[Android] Google Play ベストオブ 2020 エンターテイメント部門大賞アプリの裏側


私が勤めている [Amazia](https://amazia.co.jp/) という会社で開発している [Palfe（パルフェ）](https://play.google.com/store/apps/details?id=jp.palfe) という Android アプリがこの度 [Google Play ベストオブ 2020 日本版](https://japan.googleblog.com/2020/12/playbestof2020.html)でエンターテイメント部門の大賞をいただきました🎉

今回この Palfe というアプリの裏側について、簡単にご紹介したいと思います。


## MAD score

Palfe の [MAD score](https://goo.gle/MADscore) です。

![summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190187/1b9d1f5e-6b42-81c7-504f-2ea4cd67e936.png)

![kotlin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190187/df3ecbcc-d663-a076-8f59-a78ec7234d06.png)

![jetpack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190187/15f7d379-65f1-3b67-69b1-fa68e90d3577.png)


## 開発環境

### minSdkVersion

23 です。

### プロジェクト / Issue 管理

プロジェクトも Issue の管理も GitHub を使用しています。

Issue の管理には追加で [ZenHub](https://www.zenhub.com/) というサービスを使っており、GitHub の Issue 管理をよりやりやすくしてくれています。

### ドキュメント管理

ドキュメント管理には Qiita Team を使っています。

### Git-flow

開発には Git-flow を採用しています。

### App Bundle

App Bundle 対応済みです。

### CI/CD

CI には Bitrise を使用しています。 develop ブランチへのプルリクエストをトリガーにビルド、Unit Test、静的解析（[ktlint](https://github.com/pinterest/ktlint)、[detekt](https://github.com/detekt/detekt)、Android Lint）を実行しています。

静的解析などで問題があった場合は、[Danger](https://github.com/danger/danger) でプルリクエストにコメントされるようになっています。

リリース前のテストはステージング環境で行っていますが、ステージング環境用の APK は [Firebase App Distribution](https://firebase.google.com/docs/app-distribution) にアップロードしています。

最近別のプロジェクトで Bitrise から GitHub Actions へ移行したので、Palfe もそのうち GitHub Actions へ移行するかもしれません。


### デザイン管理

Figma を使っています。

必要な画像素材は Figma からダウンロードしてプロジェクトに組み込んでいます。

画像素材は基本的にはベクタ画像としてプロジェクトに組み込みますが、複雑な画像の場合には WebP 形式に変換して組み込みます。

## 使用ライブラリ

- [Kotlin Coroutines](https://github.com/Kotlin/kotlinx.coroutines)
  - 非同期処理で使っています
  - Flow や Channel も使っています

- [OkHttp](https://github.com/square/okhttp)、[Retrofit](https://github.com/square/retrofit)
  - 通信処理のために使っています

- [Moshi](https://github.com/square/moshi)
  - JSON のパースに使っています

- [Dagger](https://github.com/google/dagger)
  - そのうち Hilt に移行したいと考えています

- [AssistedInject](https://github.com/square/AssistedInject)
  - Dagger で公式にサポートされるようなので、そのうち削除する予定です

- [Glide](https://github.com/bumptech/glide)
  - 画像ローダーとして使っており、主に以下の理由で採用しています
      - [Picasso](https://github.com/square/picasso) は画像が見切れることがある
          - https://proandroiddev.com/using-picasso-watch-out-for-half-black-images-957bc9550888
      - カスタムの ModelLoader を実装できる（一部の画像表示のために使用）
          - https://bumptech.github.io/glide/tut/custom-modelloader.html

- [Material Components](https://github.com/material-components/material-components-android)
  - モーションによるアニメーションやダークテーマにも対応しています

- [Groupie](https://github.com/lisawray/groupie)
  - RecyclerView 内に異なる種類のレイアウトを表示するのに便利なので使用しています

- [flow-preferences](https://github.com/tfcporciuncula/flow-preferences)
  - SharedPreferences で保存しているデータに変更があった場合、Coroutine Flow で変更通知を受け取れます
  - [DataStore](https://developer.android.com/topic/libraries/architecture/datastore) が安定版になれば移行を検討しようかと考えています

- [LiveEvent](https://github.com/hadilq/LiveEvent)
  - LiveData でイベント通知を行うために使用しています
  - [SharedFlow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-shared-flow/) がリリースされたため、今後はこちらを使用することを検討しています

- [PhotoView](https://github.com/chrisbanes/PhotoView)
  - 画像を拡大表示したりするために使用しています

- [Timber](https://github.com/JakeWharton/timber)
  - ログ出力のために使用しています
  - LogCat への出力以外にも、`Timber.DebugTree` を継承したクラスを作成して `Log.e()` で Crashlytics にログが出力されるようにする設定などを行っています

- [Flipper](https://github.com/facebook/flipper)
  - デバッグのために使っています

- [Google Play Billing Library](https://developer.android.com/google/play/billing/release-notes)
  - 課金処理のために使っています

### Jetpack 系

- [Activity](https://developer.android.com/jetpack/androidx/releases/activity)
  - ViewModel を取得するための拡張関数 `viewModels()` や `OnBackPressedDispatcher` などを使っています

- [Fragment](https://developer.android.com/jetpack/androidx/releases/fragment)
  - ViewModel を取得するための拡張関する `viewModels()`、 `activityViewModels()` や `FragmentContainerView`、`FragmentFactory` などを使っています

- [Room](https://developer.android.com/jetpack/androidx/releases/room)

- [ConstraintLayout](https://developer.android.com/jetpack/androidx/releases/constraintlayout)

- [Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle)
  - LiveData、ViewModel、SavedState などを使っています

- [ViewPager2](https://developer.android.com/jetpack/androidx/releases/viewpager2)
  - 漫画を縦スクロールでも読めるように、と思って採用しました（まだ縦スクロールできるように実装していませんが。。。）
  - ViewPager2 の中で縦方向にスクロールする RecyclerView を配置した場合、リストを縦方向にスクロールしようとしても ViewPager2 の横スワイプの効きがよくてうまく操作できないことがあったので、漫画を表示する以外の箇所は普通の ViewPager を使っています

- [RecyclerView](https://developer.android.com/jetpack/androidx/releases/recyclerview)

- [Paging](https://developer.android.com/jetpack/androidx/releases/paging)
  - 直接 Paging を使っているわけではなく、Firebase-UI（後述） というライブラリを使っており、そのライブラリが Paging を使っています

- [WorkManager](https://developer.android.com/jetpack/androidx/releases/work)
    - 以前使用していましたが、現状では下位互換のために残しているだけなので削除予定です

### テスト系

- [JUnit4](https://github.com/junit-team/junit4)

- [Truth](https://github.com/google/truth)
  - アサーションライブラリです

- [MockK](https://github.com/mockk/mockk)
  - モック用のライブラリです

- [Robolectric](http://robolectric.org/)
  - JVM 上で Android 依存のコードを実行することができるテストフレームワークです

- [AndroidX Test](https://developer.android.com/jetpack/androidx/releases/test)
  - Runner と JUnit を導入しています

### Firebase 系

- [Crashlytics](https://firebase.google.com/docs/crashlytics)
  - クラッシュ解析に使っています

- [Analytics](https://firebase.google.com/docs/analytics)
  - ユーザー行動の分析に使っています

- [Firestore](https://firebase.google.com/docs/firestore)
  - ユーザーが読んだ漫画の閲覧履歴やお気に入りのデータの保存に使っています

- [AdMob](https://firebase.google.com/docs/admob)
  - 広告表示のために使っています

- [Auth](https://firebase.google.com/docs/auth)
  - ユーザー認証のために使っています

- [Firebase-UI](https://github.com/firebase/FirebaseUI-Android)
  - Firestore で保存された漫画の閲覧履歴などを RecyclerView で表示するために `FirestorePagingAdapter` というクラスを使っています

### Gradle Plugin 系

- [ktlint](https://github.com/pinterest/ktlint)
  - Gradle で Kotlin のコンパイルの実行前に、ktlint によるフォーマットのタスクを実行するように設定したりしています

- [OSS Licenses Gradle Plugin](https://developers.google.com/android/guides/opensource)
  - オープンソースライセンスの表示のために使っています

- [Gradle Play Publisher](https://github.com/Triple-T/gradle-play-publisher)
  - ビルドして出来た App Bundle を Play コンソールへアップロードするために使っています
  - バージョン 3.0.0 では Crashlytics に mapping.txt がアップロードされない不具合がありました...😢
      - https://github.com/Triple-T/gradle-play-publisher/issues/859

- [Releases Hub Gradle Plugin](https://github.com/releaseshub/releases-hub-gradle-plugin)
  - 使用しているライブラリにバージョンアップがあった場合にプルリクエストを出してくれます
  - GitHub Actions でクーロンを設定して定期的にバージョンアップがあるかどうかをチェックしています

- [Gradle Swagger Generator Plugin](https://github.com/int128/gradle-swagger-generator-plugin)
  - Palfe の Web API は Swagger でスキーマ定義されています。この Gradle Plugin で Swagger の定義ファイルからコードを生成するために使用しています
  - 生成されるコードは Web API のレスポンスとなるクラス、エラーレスポンスのクラス、Retrofit で使うインターフェースです

## アーキテクチャ

マルチモジュール + MVVM です。

### マルチモジュールについて

Google が Gradle のマルチモジュールを推奨しているため、Palfe でもマルチモジュールを採用しています。

以下のような感じでモジュールを分割しています。

```
.
├── app : アプリケーションモジュール
├── base : Android Framework に依存せず、各モジュールで使用される処理を定義するためのライブラリモジュール
├── base-android : Android 関連の各モジュールで使用される処理を定義するためのライブラリモジュール
├── data
│   ├── db : Room 関連の処理を定義したライブラリモジュール
│   ├── firestore : Firestore 関連の処理を定義したライブラリモジュール
│   ├── network : ネットワーク関連の処理を定義したライブラリモジュール
│   └── preferences : SharedPreferences 関連の処理を定義したライブラリモジュール
├── feature : 特定の機能に関する処理のためのライブラリモジュール。例えば Analytics や課金処理など。
│   ├── analytics
│   ├── billing
│   └── ...
└── ui : UI 関連の処理を定義したライブラリモジュール。画面ごとに個々にモジュールを作成している
    ├── common
    └── ...
```

### MVVM について

Google が推奨しているアーキテクチャは[こちらに記載されているような構成](https://developer.android.com/jetpack/guide)かと思いますが、Palfe では Repository がなく ViewModel で直接 API でネットワークからデータを取得したり、DB からデータを取得したりしています。

基本的にはだいたいどの画面も以下のような処理の流れになります。

- ViewModel で API や DB からデータを取得する
- 取得したデータを Activity や Fragment で必要な形式に整形し、LiveData で通知する
- Activity や Fragment では ViewModel の LiveData を監視し、値が通知されたら表示処理を行う（Data Binding も使用しています）

このような構成にしたのは、構成をシンプルにしたいという考えからで、主に以下のような理由からです。

- 別のプロジェクトで Clean Architecture のような構成を採用したが、単に API からデータを取ってきて一覧表示するだけの画面を実装するのにいくつものクラスを作成する必要があり、かなり冗長となってしまった

- 新しいメンバーが参画したときに、できる限りアーキテクチャの把握を容易にしたいため

## 課題 / やりたいこと

- メンバーの拡充
  - Android の開発メンバーは私と業務委託でもう一人の二名体制です。他のプロジェクトも掛け持ちしているため、Palfe の開発にはあまり時間を割けていない状況です
  - 興味のある方はぜひ！

- テストコード
  - 現状ではテストはあまり書けていないです

- リファクタリング
  - 基本 ViewModel にロジックを書いていく方針なので　ViewModel が肥大化している箇所があったりします。こういった箇所はもう少し適切に役割分担してコードの見通しがよくなるようにしたいと考えています
  - マルチモジュールで UI に関しては画面ごとにモジュールを作成していますが、分割の粒度が細かすぎた気がしてます。どの粒度で分割するか正直悩ましいですが、もう少し分割の粒度が大きい方がコードの見通しがよい気もしています

- In-app review、In-app updates の導入
  - 別のプロジェクトでアプリ内レビューやアプリ内アップデートの機能を実装したので、Palfe にも導入したいと考えています

- ライブラリの導入、更新
  - 複雑な設定が不要になるので、Hilt への移行は行いたいと考えています