はじめに
みなさんこんにちは。AndroidのViewModel, 便利ですよね。
Activityが再生成されるときのデータの復元をシンプルにできますが、私は次のようなことがやりたいと思いました。
やりたいこと
スコープの異なるActivity同士やActivity, Fragment間でViewModelを共有したい
図のようにFirstActivityとSecondActivityとで同じViewModelを共有できたらいいなと思う場面がありました。
具体的には、写真のビューアーで以下のようにGridな表示とListな表示切り替えられる機能です。
これら2つの画面では同じImageのListを保持しているし、その写真の情報も同じなため、同じViewModelを共有したいと思いました。
ViewModelの共有について
Fragment間でのViewModelの共有はサポートされています。
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=JA#sharing
なぜならActivityが共有なため、スコープが共通だから。(言い方があっているかは不安)
今回、私の場合はFragmentとActivity間でやり取りさせたいため, このやり方ではうまくできませんでした。
ViewModelStoreOwner
そこで、ViewModelStoreOwnerの出番です。
ViewModelStoreOwner は、ViewModelを保存しておくViewModelStoreのスコープを定義することができます。
ViewModelStoreOwnerは抽象メソッドをひとつだけもつinterfaceです。
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
流れとしては、ViewModelを共有したいActivityに、ViewModelStoreOwnerを実装して、共通のViewModelStoreを使えるようにできればと思いました。
まずは同じViewModelStoreが取得できるよう、ViewModelStoreOwnerにデフォルト実装を加えたinterfaceを用意しました。
interface PhotoViewModelStoreOwner : ViewModelStoreOwner {
override fun getViewModelStore(): ViewModelStore = // SingletonなViewModelStoreをDIなどで挿入
}
これをViewModelを共有したいActivity, Fragmentに実装します。
class PhotoGridFragment : Fragment(), SnapPhotoViewModelStoreOwner {
private val viewModel: PhotoViewModel by lazy {
PhotoViewModel.get(id, photoTag, this)
}
override fun getViewModelStore(): ViewModelStore {
return super<PhotoViewModelStoreOwner>.getViewModelStore()
}
}
class PhotoListActivity : AppCompatActivity(), SnapPhotoViewModelStoreOwner {
private val viewModel: PhotoViewModel by lazy {
PhotoViewModel.get(id, photoTag, this)
}
override fun getViewModelStore(): ViewModelStore {
return super<PhotoViewModelStoreOwner>.getViewModelStore()
}
}
そして、ファクトリーメソッドをViewModel側に用意しました。
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するといいでしょう。
override fun onDestroy() {
viewModelStore.clear()
super.onDestroy()
}
おまけ
ActivityScopeなViewModelでは、スコープの違う者同士共有できませんが、ApplicationScopeなら、Activityが破棄された後も生き残るため、共有することができます。
やり方は、ViewModelStoreOwnerの実装を、Applicationにするだけです。
class MyApplication : Application, ViewModelStoreOwner {
private val viewModelStore : ViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore = viewModelStore
}
しかし、このやり方だとclearができず、ViewModelがActivityがいなくなった後も永遠と残ってしまうため、いいやり方ではありません。