DEV Community

Cover image for How to Inject Navigation Argument Directly into View Model with Jetpack Compose and Hilt
bright inventions
bright inventions

Posted on • Originally published at brightinventions.pl

How to Inject Navigation Argument Directly into View Model with Jetpack Compose and Hilt

When using the Jetpack Compose navigation you may sooner or later come across the problem of passing arguments between the screens. The solution is fairly well described in the official Android documentation. Let's follow the documentation and go over a few issues that you might encounter on the way.


The problem


Following the official documentation we’re ending up with a NavHost setup, defined routes and the following view model setup:


class UserViewModel(
    savedStateHandle: SavedStateHandle,
    private val userInfoRepository: UserInfoRepository
) : ViewModel() {

    private val userId: String = checkNotNull(savedStateHandle["userId"])
    private val userInfo: UserInfo = userInfoRepository.getUserInfo(userId)

    // ...
}
Enter fullscreen mode Exit fullscreen mode


With this approach we don’t add a lot of code to the view model in order to retrieve the desired navigation argument. Yet there might be two issues to consider:


  • With more arguments we keep putting more code to the view model class,
  • If we want to write tests for our view model we need to mock the SavedStateHandle and any repository we’re using, like UserInfoRepository in the example above. 

Hilt to the rescue

Using Hilt we can add an in-the-middle module that could resolve SavedStateHandle and pass the actual navigation argument directly to the view model.


@Module
@InstallIn(ViewModelComponent::class)
object UserScreenNavigationArgModule {

    @Provides
    @ViewModelScoped
    fun provideUserInfo(
        savedStateHandle: SavedStateHandle,
        userInfoRepository: UserInfoRepository,
    ): UserInfo {
        val userId: String = checkNotNull(savedStateHandle["userId"])
        return userInfoRepository.getUserInfo(userId)
    }
}
Enter fullscreen mode Exit fullscreen mode



@HiltViewModel
class UserViewModel(
    private val userInfo: UserInfo
) : ViewModel() {
    // ...
}
Enter fullscreen mode Exit fullscreen mode


Just two remarks:


  • Install the module in the ViewModelComponent class
  • Narrow the scope using the @ViewModelScoped annotation 

Links


If you need to test this solution on a working example I’ve prepared one.

Sample app: https://github.com/radek-bright/demo-navigation-arguments


Side Note: Motivation


P.S. for a background reference. In the not-so-distant past, when using Dagger2 and Fragments, we could have skipped injecting Bundle objects into view models and take advantage of a custom per-fragment navigation argument module that we could include with @ContributesAndroidInjector. I’m not going to go through the process, but just for the reference the core looked like this:


@Module
interface InjectorModule {
    @ContributesAndroidInjector(modules = [ArgsModule::class])
    fun contributeMyFragment(): MyFragment
}

@Module
class ArgsModule {
    @Provides
    fun provideMyArgument(fragment: MyFragment): MyArgument =
        fragment.arguments?.getSerializable("MY_ARGUMENT_KEY") as MyArgument
}

class MyFragmentViewModel @Inject constructor(
    private val myArgument: MyArgument,
)
Enter fullscreen mode Exit fullscreen mode

By Radek Pieczątkiewicz, Android Developr @ bright inventions

Top comments (0)