Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Alex
Alex

Posted on

     

Parameter Injection for Android ViewModels

Injecting dependencies into our ViewModel is already a good practice, it keeps the implementation flexible and easy to test.

But what about parameters provided to the screen or Fragment? For example Fragment Args or Compose navigation parameters. Often something like aninit method is used to receive the parameters from the View and setup the ViewModel. This adds extra steps to our ViewModel we needs to be aware of. Therefore it would be more favourable to, not only get the dependencies, but also the parameters in the constructor.

Setup

For this example let's keep it simple and focus mainly on the handling of the parameter.

We create an App with two screens.

  1. Screen 1 is just a button. Tapping it gets a random number and navigates to Screen 2, handing the random number over as parameter.

Random Button

  1. Screen 2 receives the random number, creates a View State and simply displays the result as a text.

Fancy result

The screens are created with Jetpack Compose and the example also uses ComposesNavHost to navigate, but the same ViewModel code applies for the use of Activities and Fragments. The only difference are the types allowed to be used as parameter. We can see in the following setup, Compose Navigation only allows us to pass parameters as part of the navigation route String.

valnavController=rememberNavController()NavHost(navController=navController,startDestination="home){composable(route="home"){HomeScreen{navController.navigate("details/$it")}}composable(route="details/{randomNumber}"){valviewModel=viewModel<DetailsFlowViewModel>()DetailsScreen(viewModel=viewModel)}}
Enter fullscreen modeExit fullscreen mode

As we can see our second screen has the routedetails/{randomNumber} declaring the parameterrandomNumber.

Handle a saved state

Now to the important question. How can we retrieve the parameter in our ViewModel on the second screen after navigation?

TheSavedStateHandle class contains the information we need and it is directly injectable into the constructor of a ViewModel.

classDetailsFlowViewModel(savedStateHandle:SavedStateHandle):ViewModel(){...}
Enter fullscreen modeExit fullscreen mode

This is possible with or without the help of a dependency injection framework likeHilt.

SavedStateHandle provides us with two methods to get to our parameter

operatorfun<T>get(key:String):T?
Enter fullscreen modeExit fullscreen mode
fun<T>getStateFlow(key:String,initialValue:T):StateFlow<T>
Enter fullscreen modeExit fullscreen mode

Depending on what we want to achieve we can use either method. In our case we want to offer a View State flow from our ViewModel to the UI, therefore let's usegetStateFlow.

classDetailsFlowViewModel(savedStateHandle:SavedStateHandle):ViewModel(){valstate:Flow<DetailsState>=savedStateHandle.getStateFlow<String?>("randomNumber",null).map{valnumber=it?.toIntOrNull()?:throwIllegalArgumentException("You have to provide randomNumber as parameter of type Int when navigating to details")// call dependencies as neededvalresult="Fancy processing: $number"DetailsState(result)}}
Enter fullscreen modeExit fullscreen mode

Important: Since we are using Compose Navigation we first have to retrieve the parameter as a String before we can convert it to its actual type Int. With Fragment Args it would be possible to directly get the parameter as an Int.

One step further

We can already provide our parameter directly to the ViewModels constructor. But there is still a drawback: The ViewModel constructor does not tell us exactly what it wants, but e.g. in tests we need to know to setrandomNumber of type String to theSavedStateHandle before passing it to the constructor. Sounds like it requires a lot of knowledge of implementation details.

Wouldn't it be better if the constructor just tells us: I want to have the parameterrandomNumber of type Int.

With the help of dependency injection frameworks likeHilt we can achieve this.

To keep it short I'm not going in to details on the basic usage ofHilt in this post. In case you want to read up onHilt you can go to itsAndroid Developers tutorial

First we create aQualifier annotation, allowing us to identify our parameter to Hilt.

@Qualifier@Retention(AnnotationRetention.BINARY)annotation class RandomNumber
Enter fullscreen modeExit fullscreen mode

With theQualifierRandomNumber we can create a smallHilt module, providing our parameter in a ViewModel scope.

@Module@InstallIn(ViewModelComponent::class)objectDetailsModule{@Provides@RandomNumber,@ViewModelScopedfunprovideRandomNumber(savedStateHandle:SavedStateHandle):Int=savedStateHandle.get<String>("randomNumber")?.toIntOrNull()?:throwIllegalArgumentException("You have to provide randomNumber as parameter with type Int when navigating to details")}
Enter fullscreen modeExit fullscreen mode

We install the module inViewModelComponent making the parameter available for the lifetime of the ViewModel it is injected in. The actualprovideRandomNumber method is basically the code we had in the ViewModel earlier, with one difference. We don't use a Flow, but get the value directly.

With the module our ViewModel becomes really simple.

@HiltViewModelclassDetailsHiltViewModel@Injectconstructor(@RandomNumberrandomNumber:Int):ViewModel(){overridevalstate:Flow<DetailsState>=flow{// call dependencies as neededvalresult="Fancy processing: $randomNumber"emit(DetailsState(result))}}
Enter fullscreen modeExit fullscreen mode

We ask for the parameter we want, using theQualifier and simply use it to create our View State.

Conclusion

Using parameter injection like shown in this post, does require a little bit more code than injecting aSavedStateHandle or creating aninit method, but it separates the different aspects of our app better, allowing for a more readable and testable code.

The whole example with different variants using theSavedStateHandle,Hilt and anActivity can be found onGitHub

In case you are wondering, the same concept can be achieved usingKoin as well.

See you in the next one 👋

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I'm a long time Software Engineer developing backend, mobile and machine learning applications, with a soft spot for Android development
  • Location
    Germany
  • Work
    Senior Software Engineer
  • Joined

More fromAlex

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp