Hilt + ViewModel + Navigationを使って型安全にパラメータを渡す

f:id:numanuma08:20220209174153j:plain

こんにちは、id:numanuma08です。ひさびさにAndroidネタです。

Navigationコンポーネントを使って画面遷移を定義しているとき、Fragment間でデータをやり取りするにはargumentで宣言したデータをby navArgs()を使ってFragment上で利用できます。しかし、ViewModelで利用するときは一工夫必要でした。

AssistedInjectを使う方法がありましたが、AssistedInjectは@InstallIn(ViewModelComponent::class)した依存関係を解決できません。

zenn.dev

star-zero.medium.com

で、対策ですがSavedStateHandleをViewModelに依存させます。SavedStateHandle.getを使えばnavigationで定義した引数をViewModelでも利用できます。

developer.android.com

しかし、SavedStateHandle.getは返り値がAny?です。また引数にキーを文字列で指定しなければなりません。キーの定義や期待されるデータの型を誤るとクラッシュに繋がります。できればここは安全にやりたいところ。そう思ってドキュメントを眺めているとnavigationのアップデートでSavedStateHandleからパラメータを取り出すコードが自動生成する機能が追加されていました!また、逆にテスト用にパラメータからSavedStateHandleを生成するコードも自動生成されています。

developer.android.com

これらのコードの使い方ですが非常に簡単です。

navigationを定義する

xmlでnavigationを定義し、引数を設定します。

<fragment
    android:id="@+id/home_fragment"
    android:name="net.numa08.ui.HomeFragment"
    android:label="home_fragment"
    tools:layout="@layout/home_fragment">
    <argument
    android:name="id"
    android:defaultValue="0L"
    app:argType="long" />
</fragment>

ViewModelで次のようにパラメータを受け取ります。

@HiltViewModel
class HomeViewModel @Inject constructor(
  savedStateHandle: SavedStateHandle
) : ViewModel() {

  private val args = HomeFragmentArgs.fromSavedStateHandle(savedStateHandle) // 自動生成されたfromSavedStateHandle
  private val id = args.id

このViewModelをテストするときは次のようにtoSavedStatehandleを使います。

@Test
fun test() {
  val args = HomeFragmentArgs(id = 100L).toSavedStateHandle() // 自動生成されたtoSavedStateHandle
  val viewModel = HomeViewModel(args)

まとめ

navigationのアップデートでViewModelでも安全にパラメータを扱えるようになりました。