ViewModelOwnerをFragmentに設定し、子Fragmentとデータを共有する

MVVMパターンにおいてFragment間でデータを共有するには、ViewModelを使うのがよいとされています。ViewModelのインスタンスを複数のFragmentに挿入するには、KoinのsharedViewModel()を使うと便利です。

しかし、普通にsharedViewModel()を使うと、ViewModelOwnerはActivityになります。これで問題がなければ構わないのですが、Navigation ComponentのSingle Activityアーキテクチャを使っている場合など、各FragmentのViewModelがすべて一つのActivityに紐づいてしまうと困る場合もあります。

そこで、KoinのsharedViewModel()で、特定のFragmentをOwnerに指定しつつ別のFragmentにも同じインスタンスを挿入する方法を紹介します。

(注)Koinのバージョンは2.2.2で確認しています。比較的最近追加された機能ですので、古いバージョンでは動作しない可能性があります。

import org.koin.android.viewmodel.ViewModelOwner.Companion.from
import org.koin.android.viewmodel.ext.android.sharedViewModel

// 親フラグメント
class MainFragment: Fragment() {
    private val viewModel: SampleViewModel by sharedViewModel(owner = { from(this) })
}

// 子フラグメント
class SubFragment: Fragment() {
    private val viewModel: SampleViewModel by sharedViewModel(owner = { from(requireParentFragment()) })
}

SubFragmentMainFragmentの子供として配置されているものとします。MainFragmentではthisを、SubFragmentではrequireParentFragment()sharedViewModel()owner引数に与えることで、MainFragmentをOwnerとする共通のViewModelインスタンスを挿入することができます。

これで、MainFragmentが作成されるたびにSampleViewModelのインスタンスが作成され、なおかつSubFragmentからはMainFragmentと同じSampleViewModelのインスタンスにアクセスすることができます。

module宣言は通常通りviewModelを宣言すればOKです。

@JvmField
val appModlue = module {
    viewModel { SampleViewModel() }
}

moduleの宣言には特に手を加えず、挿入する側で挿入方法を柔軟に設定できるあたり、Koinは本当によくできてるなと感じます。公式の説明は下記にあります。もう少し具体的に書いてくれていると助かるんですけどね・・・。ってあれ?サイトのデザインがきれいになってる!

https://insert-koin.io/docs/reference/koin-android/viewmodel#custom-viewmodelstore-viewmodelstoreowner–savedstateregistryowner