【Android】MVVMについて


記事を移動しました。

https://zenn.dev/dd_sho/articles/5f32ce2942cbd2
今後は上記の記事で更新します。(2021/10/03)

Googleが推奨する MVVM について調べてみたのでまとめます。

MVVMを採用したサンプルプロジェクトのコードをこちらに挙げています。
この記事の後ろの方に一部抜粋してコードを載せていますが全体のコードが気になる方は参照してください。

MVVMの概要

MVVMの概要を図にするとこんな感じでしょうか。
MVVMを構成する要素はView、ViewModel、Modelの3つです。
(Repositoryは必須ではない)

各要素の役割

View

  • UIの更新
     データ処理の結果を画面に表示する、画面遷移、Fragmentの切り替え、etc...
  • ユーザのアクションを検知する
     ボタンのクリック、リストのスクロール、etc...

ViewModel

  • Modelからデータを取得する(Repository経由で取得することもある)
  • 取得したデータをViewで表示するための処理を行う

Model

  • データを管理する

Repository

  • ViewModelとModelを疎結合にする(必須ではない)

View、ViewModel、Modelで実装すること

それぞれの役割を果たすために以下の内容を実装する

View

ViewModelのインスタンスを保持して

  • ViewModelのデータ処理を行うメソッドを呼ぶ
    ボタンがタップされたらviewModel.loadData()を呼ぶ、etc...

  • ViewModelのLiveDataプロパティを監視する

    • ①ソースコードで監視する
      viewModel.sampleLiveData.observe(this, Observer { 更新された時の処理 })、etc...
    • ②DataBindingでxmlファイルから監視する

 ※LiveDataについてはこちら
  ざっくり言うとViewのライフサイクルを考慮してくれるObservable
 ※DataBindingについてはこちら

ViewModel

Model(or Repository)のインスタンスを保持して

  • データ処理を行うメソッドを定義する(Viewから呼ばれる)
    loadData()、updateStatus()、etc...

  • Viewにデータを渡すためのLiveDataプロパティを定義する(Viewから監視される)

  • Model(or Repository)のデータを取得するメソッドを呼ぶ
    データ処理メソッドが呼ばれたらmodel.fetchData()を呼ぶ、etc...

  • 取得したデータをViewで表示するために処理を行うメソッドを定義する(必要に応じて)
    model.fetchData()で取得したデータの内Viewが必要な値だけ取り出して
    LiveDataプロパティを更新する、etc...

Model

  • 実際にデータが保存されている場所へのアクセスを管理するクラスを用意する
    APIClient、Dao、etc...

  • アプリ内でデータを扱いやすくする独自のデータクラスを定義する
    idとnameプロパティを持ったUserクラス、etc...

Repository(実装しない場合はModel)

  • 実際にデータが保存されている場所に直接アクセスするクラスを用意する
    API、DB、Preference、etc...

  • 必要に応じて、追加/取得/更新/削除を行うメソッドを実装する

実装例

全体のコードはこちらを参照してください。

View

MainFragment.kt
class MainFragment : Fragment() {

    private  val viewModel: MainViewModel by sharedViewModel()

    // 省略

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

        binding.apply {
            viewmodel = viewModel // xml内でDataBindingの変数としてviewmodelを定義
            setLifecycleOwner(activity)
        }

        viewModel.initParameters() // 初期表示時のデータ処理
        observeViewModel() // MainViewModelのLiveDataプロパティを監視する
    }

    private fun observeViewModel() {

        viewModel.playStatus.observe(this, Observer { 
            // 省略
        })

        viewModel.themePicFileResId.observe(this, Observer { 
            // 省略
        })
    }

    // 省略

}

ViewModel

MainViewModel.kt
class MainViewModel(val context: Application)
    : AndroidViewModel(context), KoinComponent {

    var themePicFileResId = MutableLiveData<Int>() // LiveDataのプロパティ
    var playStatus = MutableLiveData<Int>() // LiveDataのプロパティ

    // 省略

    private val userSettingsRepository: UserSettingsRepository by inject()
    private lateinit var userSettings: UserSettings // 独自のデータクラス(Model)

    // 初期表示時のデータ処理
    fun initParameters() {

        userSettings = userSettingsRepository.loadUserSettings() // データ取得

        themePicFileResId.value = userSettings.themeResId  // 更新してViewに通知
        playStatus.value = PlayStatus.BEFORE_START  // 更新してViewに通知
    }

   // 省略

}

Model

UserSettings.kt
data class UserSettings(
    var levelId: Int,
    var levelName: String,

    // 省略

    var themeResId: Int) {

        // 省略

}

Repository

UserSettingsRepository.kt
class UserSettingsRepository {

    private val context = MyApplication.appContext
    private val pref = context.getSharedPreferences(UserSettings.PREF_USERSETTINGS_NAME, Context.MODE_PRIVATE) // SharedPreferenceのインスタンス

    // SharedPreferenceからデータを取得
    fun loadUserSettings(): UserSettings {

        // 省略

    }
}

まとめ

Androidの MVVM についてサンプルプロジェクトを作成してまとめてみました。
誤っている点などご指摘いただけますと助かります。