Android実装MVVM+Repositoryパターン2020年08月時点

# はじめに
1年に2本くらいAndroidのアプリ実装案件をやっていて、その度にアーキテクチャとライブラリの選定をしています。
アーキテクチャは基本的にはMVVM＋Repositoryパターンをベースにしていますが、ライブラリに関してはプロジェクトの状況によって使用するものを変えています。昨今のAndroidアプリの実装における流行り廃りの激しさも含め、毎回選定内容が変わっているので、今回の選定に関して自分用のメモとして残し、色々な方の意見をお聞きできたらと思っています。

# プロジェクトの前提条件
企業が新規事業として行うサービスで公開されるアプリです。

- 納期が短い（私がお願いされる時点でだいたい短い）
- 最終的に企業内のシステム担当に引き継ぐ可能性がある
- システム担当の方のスキルは未知数
- 予算の関係でテストコードなしでOK

こういう案件は多いです。テストコードについては省略したくないところではありますがPMFの測定段階では、次のフェーズに進む際に大きくアプリの構造を変える場合があるのでView周りのテストコードを落とすという判断をしています。ビジネスロジックはサーバ側に寄せる設計にしていますが、アプリ側で実装する場合もあるので、そういったものはテストコードを書くようにしています。

# 考え方
基本的には、Android Developersなどの公式のドキュメントと、導入例をみて考えていますが、毎回悩むのは__非同期処理__と__DI__周りのライブラリです。非同期処理でいうと`AsyncTask`、`Rx`、`coroutine`など含めて何を選択するかを、DIに関してはライブラリ以前にそもそも導入するかどうかを考えています。

また、これらのライブラリの選定に関しては**何を導入するかもそうですが、導入したライブラリをどこで使うかをはっきりさせること**を大切にしています。

この辺のライブラリは学習コストが非常に高いと思っています。__ちゃんと知っていれば__便利で、コードもスマートになったりしますが、この__ちゃんと知っていれば__のハードルが高いと思ってるので、なるべく使用箇所を限定して、ライブラリの導入意図をはっきりさせた使い方を心がけています。

アーキテクチャに関しては、アーキテクチャというより`RecyclerView`実装時の各クラスの責務を毎回迷っています。MVVMと言っていますがここだけはどうしてもいびつになってしまうのでしっかりと方針を決めるようにしています。

# 今回の選定

1．非同期処理
`coroutine`にしました。公式のドキュメントで推奨されたのは大きいです。

[Android での Kotlin コルーチン](https://developer.android.com/kotlin/coroutines?hl=ja)

ただし、今回は比較的シンプルなアプリなので、`Repository`クラス内でのデータ取得関連部分に使用をとどめ、`ViewModel`内での`viewModelScope.launch`等は使用しないようにする予定です。共通部分のみで使用することで、ソースコードを引き継いだ人が`coroutine`に詳しくなくてもある程度改修は行えるようにしています。

2．DI
`Hilt`を導入することにしました。これも公式ドキュメントの記載は大きいです。

[Hilt を使用した依存関係の注入](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja)

ただし、`ViewModel`に`Repository`クラスをDIするためと、[KTX拡張機能](https://developer.android.com/kotlin/ktx?hl=ja)を使用した、`Fragment`等への`ViewModel`のDIのみに使用することで、新規画面追加時でもお手前として記述方法さえわかってれば良いようにしました。

3．RecyclerViewの実装方法
`Data binding`は行わず、`Fragment`内で`Adapter`にデータをセットする予定です。本当は`ViewModel`に`RecyclerView`のデータを`LiveData`形式で持たせて、直接`RecyclerView`にデータをバインドしたいと思っていました。

[RecyclerView + ListAdapter + ViewModel + LiveDataでData Binding](https://tkhs0604.hatenablog.com/entry/recyclerview-databinding)

この記事のようにきれいに書ければこうなっている方が良いと思っています。
ただし、Repositoryパターンを使って、API等でデータを取ってくる場合、以下ような`Resource`クラスでラップした`LiveData`を返すようにしているので、直接データを`ViewModel`に持とうとすると、毎回`ViewModel`にレスポンスを処理するコードを書かなければならず冗長になってしまいます。また、公式ドキュメントでも以下のような記載があるので`Fragment`での値セットと判断しました。
`Fragment`で`LiveData`を監視し、データを受け取った際に、`Adapter`にセットするか、`ViewModel`にデータをセットするかも迷いどころですが、今回はリストの追加・削除等がないので前者で考えています。

[ViewModel の概要](https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ja)
> ライフサイクル対応の監視可能オブジェクト（LiveData オブジェクトなど）への変更を監視しないでください。


```kotlin:Resource.kt
class Resource<out T> private constructor(val status: Status, val data: T?, val message: String?) {
    companion object {
        fun <T> success(data: T?): Resource<T> {
            return Resource(Status.SUCCESS, data, null)
        }

        fun <T> error(msg: String?, data: T? = null): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(): Resource<T> {
            return Resource(Status.LOADING, null, null)
        }
    }
}
```
`RecyclerView`はほとんどのアプリで使用するため、毎回MVVMと言いながら実際にはきれいなMVVMになってないなと感じています。最近Flutterで実装しているときれいにMVVMにできるため、Android実装時の迷いが大きくなりました。

4．失敗談（Jack vs Kotlin)

2017年の2月くらいのプロジェクトでJava8の言語機能を使うためにJackを使うか、Kotlinを導入するか悩んで、Jackを選択したのですが、そのアプリのリリース後、7月のGoogle I/OでKotlinが正式な開発言語と発表されたときはかなり参りました。

# 主要クラスの実装

まだ改良は重ねますが、現状お知らせ一覧を表示する画面はこんな感じになっています。


`Repository`クラスの実行時だけ、`coroutine`を使用しています。

```kotlin:AppRepository.kt
@Singleton
class AppRepository @Inject constructor(
    private val apiService: ApiService
) {
    fun getAnnouncements(): LiveData<Resource<AnnouncementsResponse>> {
        return execute { apiService.getAnnouncements() }
    }

    private fun <T> execute(function: suspend () -> T): LiveData<Resource<T>> {
        return liveData(Dispatchers.IO) {
            emit(Resource.loading())
            try {
                emit(Resource.success(function()))
            } catch (exception: Exception) {
                emit(Resource.error(exception.message))
            }
        }
    }
}
```

`ViewModel`はDI周りの構文があるくらいで、処理は`Repository`クラスのメソッドを呼ぶくらいになっています。

```kotlin:AnnouncementsViewModel.kt
class AnnouncementsViewModel @ViewModelInject constructor (
    private val appRepository: AppRepository
): ViewModel() {
    fun getAnnouncements() = appRepository.getAnnouncements()
}
```

`Fragment`においてはAPIの返りを監視し、データ取得時に`Adapter`にデータをセットしています。

```kotlin:AnnouncementsFragment.kt
@AndroidEntryPoint
class AnnouncementsFragment : BaseFragment(), AnnouncementRecyclerAdapter.AnnouncementListener {
    private val viewModel: AnnouncementsViewModel by viewModels()
    private val announcementAdapter by lazy { AnnouncementRecyclerAdapter(this) }
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? =
        FragmentAnnouncementsBinding.inflate(inflater, container, false).also {
            it.handler = this
            it.viewModel = viewModel
            it.lifecycleOwner = this
            it.announcementRecyclerView.adapter = announcementAdapter
        }.root

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        loadAnnouncements()
    }

    private fun loadAnnouncements() {
        viewModel.getAnnouncements().observe(viewLifecycleOwner, Observer { resource ->
            handleResource(resource,
                successProcess = {
                    it.data?.let { response ->
                        announcementAdapter.bindAnnouncements(response.announcements)
                        announcementAdapter.notifyDataSetChanged()
                    }
                })
        })
    }
}
```

# 最後に
とはいえ、アーキテクチャにしろライブラリにしろ好きにするべきだし、それでいいと思います。ただ最近、何か割れてきた（実装のパターンが増えた）気がしたので現時点の考えをまとめてみました。※選択肢が増えることは良いと思います。