RIZAPテクノロジーズ株式会社

採用サイト

2024.12.17
技術・カンファレンス

HiltのAssistedInject

#Android #技術ブログ
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 メソッドを呼んでいる形です。

これで、StoreDetailScreenStoreDetailViewModel がコンストラクタで期待する 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

 

 

ENTRY