LoginSignup
32
27

More than 3 years have passed since last update.

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

Posted at

はじめに

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

やりたいこと

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

Activity間でViewModelを共有したい.001.jpeg

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

具体例.002.png

これら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がいなくなった後も永遠と残ってしまうため、いいやり方ではありません。

32
27
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
27