閉じる

引数を必要とする ViewModelの利用

引数を持つ 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()


*1 引数を必要としない ViewModelも含む

*2 以前は sharedViewModelなるものを使っていたようだけど、deprecatedになった

コメントを残す

メールアドレスが公開されることはありません。必須項目には印がついています *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)