【Android】MVVMについて

#記事を移動しました。
https://zenn.dev/dd_sho/articles/5f32ce2942cbd2
今後は上記の記事で更新します。（2021/10/03）

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

MVVMを採用したサンプルプロジェクトのコードを[こちら](https://github.com/sEmoto0808/kt-Meditation-demo)に挙げています。
この記事の後ろの方に一部抜粋してコードを載せていますが全体のコードが気になる方は参照してください。

# MVVMの概要
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139470/7058eaf7-cc15-7c60-2c14-eeee7986c64d.png)

MVVMの概要を図にするとこんな感じでしょうか。
MVVMを構成する要素は**View、ViewModel、Model**の３つです。
（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についてはこちら](https://developer.android.com/topic/libraries/architecture/livedata)
　　ざっくり言うとViewのライフサイクルを考慮してくれるObservable
　※[DataBindingについてはこちら](https://developer.android.com/topic/libraries/data-binding)

### 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...

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

# 実装例
全体のコードは[こちら](https://github.com/sEmoto0808/kt-Meditation-demo)を参照してください。

### View
```kotlin: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
```kotlin: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
```kotlin:UserSettings.kt
data class UserSettings(
    var levelId: Int,
    var levelName: String,

    // 省略

    var themeResId: Int) {

        // 省略

}
```

### Repository
```kotlin: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** についてサンプルプロジェクトを作成してまとめてみました。
誤っている点などご指摘いただけますと助かります。