Activity, Fragmentを跨いでViewModelを共有する

# はじめに
みなさんこんにちは。AndroidのViewModel, 便利ですよね。
Activityが再生成されるときのデータの復元をシンプルにできますが、私は次のようなことがやりたいと思いました。

# やりたいこと
**スコープの異なるActivity同士やActivity, Fragment間でViewModelを共有したい**

![Activity間でViewModelを共有したい.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28622/8e959b21-fcb8-6876-06bb-86278d15443a.jpeg)

図のようにFirstActivityとSecondActivityとで同じViewModelを共有できたらいいなと思う場面がありました。
具体的には、写真のビューアーで以下のようにGridな表示とListな表示切り替えられる機能です。

![具体例.002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28622/c864493a-d267-9823-2158-881b519de007.png)

これら２つの画面では同じImageのListを保持しているし、その写真の情報も同じなため、同じViewModelを共有したいと思いました。

# ViewModelの共有について
Fragment間でのViewModelの共有はサポートされています。
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=JA#sharing
なぜならActivityが共有なため、スコープが共通だから。（言い方があっているかは不安）

今回、私の場合はFragmentとActivity間でやり取りさせたいため, このやり方ではうまくできませんでした。

# ViewModelStoreOwner
そこで、ViewModelStoreOwnerの出番です。
[ViewModelStoreOwner](https://developer.android.com/reference/android/arch/lifecycle/ViewModelStoreOwner) は、ViewModelを保存しておくViewModelStoreのスコープを定義することができます。

ViewModelStoreOwnerは抽象メソッドをひとつだけもつinterfaceです。

```kotlin
public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}
```

流れとしては、ViewModelを共有したいActivityに、ViewModelStoreOwnerを実装して、共通のViewModelStoreを使えるようにできればと思いました。

まずは同じViewModelStoreが取得できるよう、ViewModelStoreOwnerにデフォルト実装を加えたinterfaceを用意しました。

```kotlin
interface PhotoViewModelStoreOwner : ViewModelStoreOwner {
     override fun getViewModelStore(): ViewModelStore = // SingletonなViewModelStoreをDIなどで挿入
}
```

これをViewModelを共有したいActivity, Fragmentに実装します。

```kotlin
class PhotoGridFragment : Fragment(), SnapPhotoViewModelStoreOwner {
     private val viewModel: PhotoViewModel by lazy {
        PhotoViewModel.get(id, photoTag, this)
     }
     override fun getViewModelStore(): ViewModelStore {
        return super<PhotoViewModelStoreOwner>.getViewModelStore()
     }
}
```

```kotlin
class PhotoListActivity : AppCompatActivity(), SnapPhotoViewModelStoreOwner {
     private val viewModel: PhotoViewModel by lazy {
        PhotoViewModel.get(id, photoTag, this)
     }
     override fun getViewModelStore(): ViewModelStore {
        return super<PhotoViewModelStoreOwner>.getViewModelStore()
     }
}

```

そして、ファクトリーメソッドをViewModel側に用意しました。

```kotlin
companion object {
        fun get(
            id: Int,
            photoTag: String?,
            viewModelStoreOwner: SnapPhotoViewModelStoreOwner
        ): PhotoViewModel = ViewModelProvider(viewModelStoreOwner, PhotoViewModelFactory(id, photoTag))
                .get(photoTag ?: "all", PhotoViewModel::class.java)
}
```

これで、ViewModelを共有することができます。
なお、ViewModelStoreは明示的にclearしないとViewModelが溜まってしまうため、スコープの長い方のActivity or Fragment のon Destoryでclearするといいでしょう。

```kotlin
override fun onDestroy() {
        viewModelStore.clear()
        super.onDestroy()
}
```

# おまけ
ActivityScopeなViewModelでは、スコープの違う者同士共有できませんが、ApplicationScopeなら、Activityが破棄された後も生き残るため、共有することができます。
やり方は、ViewModelStoreOwnerの実装を、Applicationにするだけです。

```kotlin
class MyApplication : Application, ViewModelStoreOwner {
    private val viewModelStore : ViewModelStore = ViewModelStore()
    override fun getViewModelStore(): ViewModelStore = viewModelStore
}

```

しかし、このやり方だとclearができず、ViewModelがActivityがいなくなった後も永遠と残ってしまうため、いいやり方ではありません。