HiltのAssistedInject
こんにちは。DX推進本部/プロダクト開発統括1部の北村です。普段はchocoZAPの Android アプリ開発を担当しています。
本日は Hilt で ViewModle に AssistedInject する方法について共有します。
AssistedInject とは
Dagger や Hilt で依存注入するとき、一部の値だけを自分で指定して注入できる仕組みのことです。
Android で JetpackCompose を用いた開発を行う際、ViewModel の inject を hiltViewModel() 関数を用いて行うと思いますが、このときにも利用できます。
利用したいシーンとしては画面に紐づく ViewModel でロジックを実行するときに必要なデータが、画面遷移時のパラメータとして渡ってくる時が考えられます。
例えば、店舗の一覧から店舗を選択し、店舗詳細画面を表示する、というアプリの例を考えます。店舗詳細画面を表示する際に店舗を示す ID を用いて API を叩く必要があります。
この ID は遷移元からパラメータとして渡ってくるのです。
こういった場合、ID を ViewModel に渡して API を叩きたくなり AssistedInject を使いたくなります。
ViewModel を AssistedInject 可能にする
まずは ViewModel で AssistedInject を受け入れられるようにコードを書きます。
@HiltViewModel(assistedFactory = StoreDetailViewModel.Factory::class) // 1
class StoreDetailViewModel @AssistedInject constructor( // 2
@Assisted private val storeId: StoreId, // 3
private val storeRepository: StoreRepository,
) : ViewModel() {
@AssistedFactory // 4
interface Factory {
fun create(storeId: StoreId): StoreDetailViewModel
}
...// 1 で HiltViewModel の assistedFactory として、@AssistedFactory が付与された Factory を指定します。これが 4 に書かれています。
// 2 では AssistedInject したい ViewModel のコンストラクタに @AssistedInject を指定します。
// 3 では AssistedInject で注入したい値を @Assisted アノテーションをつけて指定します。
// 4 では 1 で指定している Factory を定義します。interface 名やメソッド名に特に指定はありませんが、StoreDetailViewModel を返すように定義しましょう。
@AssistedFactory を付与する interface のメソッドを複数定義することはできません。例えば以下のようにはできないということです。
@AssistedFactory
interface Factory {
fun create(id: StoreId): StoreDetailViewModel
fun create(): StoreDetailViewModel
}
...これを行うと以下のようなエラーが出てビルドはできません。
[ksp] /Users/.../StoreDetailViewModel.kt:23: The @AssistedFactory-annotated type should contain a single abstract, non-default method but found multiple: [com.example.ui.store.StoreDetailViewModel.Factory.create(com.example.core.Id), com.example.ui.store.StoreDetailViewModel.Factory.create(com.example.core.Id)]
hiltViewModel() 関数を使い、ViewModel の AssistedInject を実行する
StoreDetailViewModel を作成するときに hiltViewModel 関数を使っていたとします。そのコードを以下のように記載しましょう。
fun NavGraphBuilder.StoreNavGraph(navController: NavHostController) {
composable<StoreDetailRoute> {
val routeParams = it.toRoute<StoreDetailRoute>()
val storeId = StoreId(
value = routeParams.id,
)
StoreDetailScreen(
viewModel = hiltViewModel<StoreDetailViewModel, StoreDetailViewModel.Factory> { // 1
it.create(storeId) // 2
},
)
}
}// 1 では hiltViewModel 関数の型パラメータとして ViewModel の型である StoreDetailViewModel を指定し、加えて StoreDetailViewModel.Factory を指定しています。
// 2 では it から create メソッドを呼んでいますが、この it は StoreDetailViewModel.Factory です。よって StoreDetailViewModel.Factory に定義した create メソッドを呼んでいる形です。
これで、StoreDetailScreen の StoreDetailViewModel がコンストラクタで期待する StoreId が Inject された状態となり、ViewModel 内では StoreId を利用する処理を実行できます。
まとめ
hiltViewModel 関数は AssistedInject を簡単に利用できる仕組みを持っています。利用するためには、ViewModel で以下を行います。
コンストラクタに @AssistedInject をつける
依存注入されたいコンストラクタ引数に @Assisted をつける
ViewModelを生成して返却するメソッドを持ったinterfaceを作成し、@AssistedFactory をつける
@HiltViewModel(assistedFactory = ) に @AssistedFactory が付いた interface を class で指定する
ViewModel を生成する hiltViewModel() 関数では以下を行います。
hiltViewModel() 関数にて、対象ViewModelと @AssistedFactory が付いたinterfaceを型パラメータに指定し、引数creationCallbackに関数を渡しつつ、関数内で @AssistedFactory が付いた interface のメソッドを呼び ViewModel を返す。
[参照]
https://dagger.dev/dev-guide/assisted-injection.html
https://dagger.dev/hilt/view-model.html
関連する求人情報