【Android】ActivityとFragmentでのViewModelの持たせ方
Android開発でActivityやFragmentにViewModelを実装することは非常に大きなメリットがあります。
一方で、FragmentでのViewModelは基本的には各Fragmentで独立した個別のものとなるため、例えば異なるFragmentのViewModel間でデータのやり取りを行いたい場合などは少しだけ工夫が必要です。
ViewModelの持たせ方・設計のやり方は人やプロジェクトの性質などによって様々ですが、個人的に行っているやり方をメモとして以下に記載します。
ViewModelの基本的な実装方針
- 一つのActivity配下で共有したいデータはメインActivityのViewModelに実装
- 同一Activity配下の特定の子Fragment間でやり取りが必要な場合は、必要なFragmentに親ActivityをオーナーとしたViewModelを実装して共有
- ActivityによらずにFragmentでやり取りを行いたい場合は、独立したViewModelオーナーを実装して共有
1. 一つのActivity配下でのデータ共有
これに関しては特に難しい事はなく、単にActivityに実装したViewModelを子Fragmentから呼び出すだけです。
lateinit var mainActivityViewModel: SomeMainActivityViewModel
private set
override fun onCreatea(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainActivityViewModel = ViewModelProvider(this)[SomeMainActivityViewModel::class.java]
}
// アプリで実装するActivityが一つだけの場合は、以下のようなviewModels拡張の実装でも不都合はない
// val mainActivityViewModel: SomeMainActivityViewModel by viewModels()
// 子Fragmentからは、親Activityと実装されているViewModelの型を参照して呼び出す
lateinit var parentActivityViewModel: SomeMainActivityViewModel
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
parentActivityViewModel = ViewModelProvider(requireActivity())[SomeMainActivityViewModel::class.java]
}
// 以下の実装でもOKだが、Fragmentを高速に切り替えた時などに正しく参照できない可能性あり
// var parentActivityViewModel: SomeMainActivityViewModel by activityViewModels()
尚、上記の例では、拡張として定義されている「by viewModels()」などを用いるのを主体にはしていませんが、アプリの動作によってはviewModels()拡張だとViewModelが正しく参照されないケースもあるため、少し長くはなるものの確実な方法をとっています(他の記事を参照のこと)。これ以降での実装例も同様です。
2. 同一Activity配下での特定のFragment間での共有
こちらは上記の1と似ていて、Fragment間で共有したいViewModelを親Activityをオーナーにして呼び出します。
// 以下の実装を共有したいFragmentにそれぞれ実装することでViewModelによるデータ共有が可能となる
// "by activityViewModels()"での実装でも不都合が無ければOK
lateinit var shareViewModel: SomeShareViewModel
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
shareViewModel = ViewModelProvider(requireActivity())[SomeShareViewModel::class.java]
}
3. ActivityによらずにFragment間でViewModelを共有
Activityによらず自由にFragment間でViewModelを共有したい場合は、独自にViewModelのオーナーを設ける必要があります。
が、特にこれも難しい実装ではなく、ViewModelStoreOwnerインターフェイスを実装したクラスのインスタンスをViewModelのオーナーとして設定し呼び出す実装にすれば実現できます。
注意点としては、オーナーのインタンスは同一でないとダメなので、Singletonなどで同一のインスタンスを保持しておく実装が必要です。
class SampleViewModelOwner private constructor() : ViewModelStoreOwner {
companion object {
val instance = SampleViewModelOwner()
}
private val vmStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore {
return vmStore
}
}
// 以下を必要なFragmentに実装
lateinit var globalShareViewModel: SomeGlobalShareViewModel
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
globalShareViewModel = ViewModelProvider(SampleViewModelOwner.instance)[SomeGlobalShareViewModel::class.java]
}