この記事はZOZO Advent Calendar 2023 シリーズ 3の1日目の記事です。
概要
Android Architecture ComponentsのViewModelを利用する機会は多いかと思います。Hiltと一緒に使用すると次のようになります。
@HiltViewModel
class SampleViewModel @Inject constructor() : ViewModel() {
// ViewModelの実装
}
この時、次のようにコンストラクタにパラメータを持たせ、ViewModelのインスタンス作成時に任意の値を与えたい場合が往々としてあります。
@HiltViewModel
class SampleViewModel @Inject constructor(
val name: String,
) : ViewModel() {
// ViewModelの実装
}
InitializerViewModelFactoryによるパラメータの受け渡し
ViewModelProvider.Factoryを実装する方法がありますが面倒です。InitializerViewModelFactoryを使うと簡単に書けます。viewModelFactoryやinitializerといったDSLでViewModelFactoryを構築することが可能です。
@Composable
fun SampleScreen() {
val viewModel: SampleViewModel = viewModel(
factory = viewModelFactory {
initializer {
SampleViewModel(
name = "horie1024",
)
}
},
)
}
さらに、viewModelはinitializerを受け取れるので、より簡潔に書けます。
@Composable
fun SampleScreen() {
val viewModel: SampleViewModel = viewModel {
SampleViewModel(
name = "horie1024",
)
}
}
HiltによるDIとの共存
ViewModelが例えばSampleRepositoryに依存している場合を考えます。
@HiltViewModel
class SampleViewModel @Inject constructor(
val name: String,
val repository: SampleRepository
) : ViewModel() {
// ViewModelの実装
}
SampleRepositoryは次のような形で、SampleViewModelにはSampleRepositoryの実装SampleRepositoryImplがHiltによってDIされます。
interface SampleRepository {}
class SampleRepositoryImpl @Inject constructor(): SampleRepository {}
この場合にInitializerViewModelFactoryでSampleViewModelのインスタンスを作ろうとすると、SampleRepositoryImplのインスタンスを自分で用意する必要があります。
@Composable
fun SampleScreen() {
val viewModel: SampleViewModel = viewModel {
SampleViewModel(
name = "horie1024",
repository = SampleRepositoryImpl()
)
}
}
nameには自分で任意の値を渡したいですが、SampleRepositoryのインスタンスはHitlに用意してもらえると嬉しいです。
SavedStateHandleでのパラメータの受け渡し
AssitedInjectを利用する方法もあるのですが、今回はSavedStateHandleを介してViewModelにパラメータを渡すことにしました。
viewModelでViewModelのインスタンスを作成する際にextras
を指定できるのでここでMutableCreationExtrasを使い指定したパラメータをセットします。MutableCreationExtrasにはdefaultViewModelCreationExtras
を渡します。
viewModel Composableの実装でもdefaultViewModelCreationExtras
を取得してextrasにセットしています。
@Composable
fun SampleScreen() {
val owner = LocalViewModelStoreOwner.current
val defaultViewModelCreationExtras =
(owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
?: CreationExtras.Empty
val viewModel: SampleViewModel = viewModel(
extras = MutableCreationExtras(defaultViewModelCreationExtras).apply {
set(DEFAULT_ARGS_KEY, bundleOf(NAME_KEY to "horie1024"))
},
)
}
これで次のようにsavedStateHandleから値を取得できます。
@HiltViewModel
class SampleViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
val repository: SampleRepository
) : ViewModel() {
val name = savedStateHandle.get<String>(NAME_KEY)
// ViewModelの実装
companion object {
const val NAME_KEY = "name"
}
}
HiltViewModelExtensions.withCreationCallback
Daggerの次のリリースで導入されるようですが、@HiltViewMmodel
と@AssistedInject
を統合して利用できるようになるそうです1。
SampleViewModelを例にすると実装は次のようになります。 ComposeからもhiltViewModel
を使って利用できるようになりそうなので楽しみです。
2024/2/6追記: Composeで hiltViewModel
からの利用もできるようになりました https://qiita.com/nkshigeru/items/54a07ba1ce03bf24716f
@HiltViewModel(assistedFactory = SampleViewModelFactory::class)
class SampleViewModel @AssistedInject constructor(
@Assisted val name: String,
val repository: SampleRepository
) : ViewModel() {
// ViewModelの実装
}
@AssistedFactory
interface SampleViewModelFactory {
fun create(val name: String): SampleViewModel
}
@AndroidEntryPoint
class SampleActivity : AppCompatActivity() {
private val name = "horie1024"
private val sampleViewModel by viewModels<SampleViewModel>(
extrasProducer = {
defaultViewModelCreationExtras.withCreationCallback<
SampleViewModelFactory> { factory ->
factory.create(name)
}
}
)
}
まとめ
簡単にですがJetpack ComposeでViewModelに任意のパラメータを渡す方法についてまとめました。
明日のAdvent Calendarの担当は @ssssota さんです。どんな記事が投稿されるのか楽しみです。
参考リンク
- https://star-zero.medium.com/initializerviewmodelfactory%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6viewmodel%E3%82%92%E7%94%9F%E6%88%90-25f46c6cb4e1
- https://star-zero.medium.com/viewmodel%E3%81%A8creationextras-c7d586b7a2c6
- https://github.com/google/dagger/issues/2287
- https://dagger.dev/hilt/view-model.html
-
12/2追記: リリースされました。https://github.com/google/dagger/releases/tag/dagger-2.49 ↩