はじめに

ViewModelは現在多くのプロジェクトで利用されているかと思いますが、LiveDataが多くなってくると管理するのが大変になってきます。最終的にはUiModel(UiState)を使ってLiveDataの数を少なくし、ViewModelに関するコードをすっきりさせるのが目的です。今回は最初の段階としてsealed classを使ってViewModelをきれいに書く方法です。

対象

  • kotlinが読める方
  • ViewModelを利用している方(MVVMのViewModelというよりはAndroidのViewModelライブラリ)


LiveDataの型をどうしていますか?

例えば画面に表示する文字列をViewModelで管理する場合、下記のようにLiveDataを用意します。

class MyViewModel : ViewModel() {
    private val _text = MutableLiveData<String>("") // 初期化方法は導入しているライブラリによっても違うのでお好きな方法で
    val text: LiveData<String> = _text
    …
}

開発中や開発初期の場合はこれでも良いですが、あとから「文字列はサーバから通信して取得したいな」とか「文字列を使用したら空文字にしたりデフォルト値にしたい」とかいろんな要望があとから出てくることもあると思います。今回は通信中を表すisLoadingを下記のように実装します。

class MyViewModel : ViewModel() {
    private val _text = MutableLiveData<String>("")
    val text: LiveData<String> = _test
    private val _isLoading = MutableLiveData<Boolean>(false)
    val isLoading: LiveData<Boolean> = _isLoading
    …
}


LiveDataに状態を持たせる

まだLiveDataは2つなので、そんなに問題にはなりませんが、GUI上で表示したいデータや扱いたいデータが多くなってくるとLiveDataの数がどんどん増えてしまいます。このような場合は新しくsealed class、object、data classを利用してすっきり書けるようになります。下記のようなクラスを用意します。

sealed class TextState {
    data class Loaded(val text: String) : TextState()
    object Loading : TextState()
}


ViewModelをすっきりさせる

TextStateを作ったことにより、MyViewModelは下記のように書き直すことができます。

class MyViewModel : ViewModel() {
    private val _textState = MutableLiveData<TextState>(TextState.Loading)
    val textState: LiveData<TextState> = _textState
    …
}

このように実装することでLiveDataの数が一つ減って文字列と状態を1つのLiveDataで管理できるようになったので、すっきりしました。あとは通信中の場合_textState.postValue(TextState.Loading)、通信完了時は_textState.postValue(TextState.Loaded(“complete”))とするとLiveDataを観測する側は通信中、通信完了及び取得した文字列を扱えるようになりました。

class MyFragment : Fragment() {
    override fun onViewCreated((view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        myViewModel.textState.observe(viewLifecycleOwner) { textState ->
            textView.text = when (textState) {
                is TextState.Loading -> "loading"
                is TextState.Loaded -> textState.text
                // sealed classなのでelseを書く必要がありません
            }
        }
        /*
        これが不要になりました。
        myViewModel.isLoading.observe(viewLifecycleOwner) {
        }
        */
    }
    …
}

まとめ

LiveData内の変数の型をこのように変更するとViewModel内のコードが短くなり、それを観測する側のFragmentやActivityもLiveDataの数だけobserveする必要がなくなったので、見通しが良くなったのではないでしょうか?もし通信に失敗した場合の状態も追加したいのであればTextStateにobject LoadError : TextState()とかを追加すればコードの変更量を少なく、エラー表示にも対応でき、変更に強いViewModelを作ることが可能です。