Hiltのナビゲーション引数で SavedStateHandle から卒業する

なぜ Hilt 2.49+ と @AssistedInject が、完全な型安全性を備えた引数渡しの「正解」と言えるのか。

 

🤔 問題点:静的な依存関係と動的なデータの混在

Android開発において、ViewModelにランタイム引数(実行時引数)を注入することは、常に議論の的となってきました。

その本質は、「静的依存関係」(DIによって管理されるリポジトリなど)と、「動的データ」(画面遷移時に渡されるナビゲーション引数など)の対立にあります。

適切なDIパターンを適用せずにこの問題に対処しようとすると、高確率でアンチパターンに陥ります。たとえば、一時的な状態をシングルトン(Singleton)内に保持してしまうと、状態の汚染や画面間でのデータ漏洩といった致命的なバグを引き起こす危険性があります。

 

🤔 解決策:ハイブリッド注入を実現する @AssistedInject

この「静的」と「動的」のギャップを埋めるための最適なツールが、@AssistedInject です。これを使用することで、DI(Hilt)が管理するオブジェクトと、実行時に受け取るランタイムパラメータをスマートに組み合わせた「ハイブリッド注入」が可能になります。

通常の @Inject は不要 :
@AssistedInject を使用する場合、コンストラクタに通常の @Inject を付ける必要はありません(というか、付けてはいけません)。

関心の分離 :
リポジトリなどの依存関係は Hilt が自動で供給し、ナビゲーション引数などの動的なデータだけを手動で安全に渡す、という明確な役割分担(関心の分離)が実現します。

クリーンなコードへ :
これにより、バグの温床になりがちだった危険な lateinit var による後からの初期化コードとは、完全におさらばできます。


// ViewModel 実装

@HiltViewModel(assistedFactory = RouteBViewModel.Factory::class) 
class  RouteBViewModel  @AssistedInject constructor( 
    private val repository: MyRepository,
    @Assisted val navKey: RouteB,
) : ViewModel() { 

    @AssistedFactory
    interface Factory { 
        fun create (navKey: RouteB) : RouteBViewModel 
    } 
}

 

🤔 実装:assistedFactory と hiltViewModel の組み合わせ

公式の nav3-recipes リポジトリでは、Hilt 2.49 以降で導入された最新の標準的な実装パターンが示されています。

ファクトリの宣言 :
@HiltViewModel(assistedFactory = ...) を使用して、Assisted Factory を ViewModel に直接リンクさせます。

2つの型パラメータ: Compose 側では、hiltViewModel() のように両方の型を明示的に指定して呼び出します。

安全なライフサイクル管理 :
creationCallback を利用することで、ナビゲーションのバックスタック管理(Lifecycle)を壊すことなく、安全にランタイム引数を渡すことができます。


// UI(ナビゲーション定義)

 entry<RouteB> { key -> 
    val viewModel = hiltViewModel<RouteBViewModel, RouteBViewModel.Factory>( 
        creationCallback = { factory -> 
            factory.create(key) 
        } 
    ) 
    ScreenB(viewModel = viewModel) 
}

 

🤔 メリット:100%の型安全性を実現し、SavedStateHandle のボイラープレートを完全に排除

このアーキテクチャ(Navigation 3 + @AssistedInject)に移行することで、コードベースの品質は大幅に向上します。

完全な型安全性(100% Type-Safe):
かつての文字列ベースのキー(string のキー指定)は過去のものです。ナビゲーション引数は、Route(Serializable等で定義された型)として、厳密に型指定されたオブジェクトのまま直接安全に渡されます。

SavedStateHandle からの解放 :
単に次の画面へ引数を渡すだけのために、SavedStateHandle を使ってごにょごにょと値を書き出したり読み出したりする定型文(ボイラープレート)はもう一切不要になります。

圧倒的なリファクタリングのしやすさ :
すべてがコンパイル時にチェックされるため、引数の追加・削除・変更といったリファクタリングが、一瞬かつ確実に(ランタイムエラーの心配なく)行えるようになります。

 

🤔 まとめ

Hilt と最新の Jetpack Navigation の相乗効果により、画面間の引数の受け渡しは驚くほどスムーズかつ洗練されたものになりました。もはや、型安全性を犠牲にしたり、不自然なアーキテクチャで回避策(ワークアラウンド)を講じたりする必要はありません。

@AssistedInject と、進化した hiltViewModel() API を組み合わせることで、「完璧な型安全性」と「クリーンなハイブリッド依存性注入(DI)」を今すぐあなたのプロジェクトに導入できます。


Related Categories :  AndroiddaggerDevelopmemtGoogleJetpackComposeKMPKotlinKotlin Multiplatform MobileRecommendedReviewTrending