引数を持つ Activityから Fragmentへ引数を引き継いで、更に ViewModelへなんてことをやっていたけど、Activityから直接 ViewModelを作ってやれば済む話だった。
引数がデータベースに対する識別子だったりすると const扱いで ViewModelや Modelへ渡せるのでスッキリもする。
ViewModelの場合
class HogeActivyt : AppCompatActivity() {
〜
private val viewModel by viewModels<HogeViewModel>()
class HogeViewModelFactory(val ID: Long) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return HogeViewModel(
ID
) as T
}
}
override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
return HogeViewModelFactory(intent.getLongExtra(NAME_ID, 0))
}
〜
}
class HogeFragment : Fragment() {
〜
private val viewModel by activityViewModels<HogeViewModel>()
〜
}
class HogeViewModel(private val ID: Long) : ViewModel() {
val model = HogeModel(ID)
〜
}
AndroidViewModelの場合
こちらは Applicationを引数としてとる ViewModelカスタムファクトリが予め定義されている感じで、今回の件はこれの応用なんだな。
class HogeActivyt : AppCompatActivity() {
〜
private val viewModel by viewModels<HogeViewModel>()
class HogeViewModelFactory(val application: Application, val ID: Long) : ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return HogeViewModel(
application, ID
) as T
}
}
override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
return HogeViewModelFactory(application, intent.getLongExtra(NAME_ID, 0))
}
〜
}
class HogeFragment : Fragment() {
〜
private val viewModel by activityViewModels<HogeViewModel>()
〜
}
class HogeViewModel(app: Application, private val ID: Long) : AndroidViewModel(app) {
val model = HogeModel(ID)
〜
}
単一の ViewModelが対象の場合はこれで問題ないのだけど、複数の ViewModel*1 を使おうとすると同じファクトリから複数の ViewModelを生成しようとして落ちることになる。
そんな時は複数 ViewModel対応のファクトリを作ってやることになる。
class HogeActivyt : AppCompatActivity() {
〜
private val mFactory = InitializerViewModelFactoryBuilder().apply {
addInitializer(ArgsViewModel::class) {
ArgsViewModel(
args
)
}
addInitializer(NonArgViewModel::class) {
NonArgViewModel()
}
}.build()
override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
return mFactory
}
private val argsViewModel by viewModels<ArgsViewModel>()
private val aonArgViewModel by viewModels<NonArgViewModel>()
〜
}
viewModelFactoryというものも用意されていて、かなり省略して書くことも可能。
class HogeActivyt : AppCompatActivity() {
〜
private val mFactory = viewModelFactory {
initializer {
ArgsViewModel(args)
}
initializer {
NonArgViewModel()
}
}
override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
return mFactory
}
private val argsViewModel by viewModels<ArgsViewModel>()
private val nonArgViewModel by viewModels<NonArgViewModel>()
〜
}
FYI: InitializerViewModelFactoryを使ってViewModelを生成
ViewModelの保存や復元を考えると SavedStateHandleへの対応も考えないといかんのかもしれんけど、後にしよう。
2022/11/14追記
SavedStateHandleへの対応は以外に簡単だった模様。
class HogeActivyt : AppCompatActivity() {
〜
private val mFactory = viewModelFactory {
initializer {
ArgsViewModel(args)
}
initializer {
NonArgViewModel(createSavedStateHandle())// <==
}
}
override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
return mFactory
}
private val argsViewModel by viewModels<ArgsViewModel>()
private val nonArgViewModel by viewModels<NonArgViewModel>()
〜
}
また、Activity – Fragment間の共有だけなら単純に activityViewModelsと viewModelsの組み合わせだけだとで良いのだけど、Fragment – Fragment間に限った共有をしたい場合はこんなふうに書けば良し。(例えば Fragmentから DialogFragmentを表示して ViewModelを共有)*2
class HogeDialogFragment : DialogFragment() {
〜
private val viewModel by viewModels<HogeViewModel>(ownerProducer = { requireParentFragment() })
〜
}
class BaseFragment : Fragment{
〜
private val viewModel by viewModels<HogeViewModel>()
〜
fun showDialog(){
HogeDialogFragment().show(childFragmentManager, null)
}
〜
}
FYI: parentFragmentのスコープで生成するparentViewModels()