You are using hilt for dependency injection in your android project. You may encounter a situation where you need to provide some dynamic dependencies to your ViewModel. In this situation you can use Assisted Injection in ViewModel. For the example I will take the project that we created in the last tutorial.
You can check the last ktor android client tutorial to get the source code of the project, as that project is required for this tutorial.
Table of Contents
Need for Assisted Injection
Once you have got the project from the last tutorial, open it in android studio and go to MainViewModel.kt .
You will see the following code in this file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@HiltViewModel class MainViewModel @Inject constructor( private val repository: MoviesRepository ) : ViewModel() { private val _movies = MutableStateFlow<Resource<List<Movie>>?>(null) val movies: StateFlow<Resource<List<Movie>>?> = _movies init { viewModelScope.launch { _movies.value = Resource.Loading _movies.value = repository.getPopularMovies() } } } |
The above ViewModel is annotated with @HiltViewModel , and @Inject constructor ; this means for this ViewModel, hilt will provide the required parameters. In this case we have only one parameter that is our repository.
Now assume a situation where we need to provide one more parameter movieId: Int . This time movieId needs to be dynamic, as for every movie the movieId will change. In this case hilt cannot provide the movieId. Now for this situation we have two parameters for our MainViewModel .
- private val repository: MoviesRepository : Hilt will provide this instance.
- private val movieId: Int : This is a dynamic value, that we want to provide manually.
For situation like this, we can use Assisted Injection in ViewModel.
Now let’s understand how to perform an Assisted Injection in this ViewModel.
Assisted Injection in ViewModel
Modify the ViewModel like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class MainViewModel @AssistedInject constructor( private val repository: MoviesRepository, @Assisted private val movieId: Int, ) : ViewModel() { private val _movies = MutableStateFlow<Resource<List<Movie>>?>(null) val movies: StateFlow<Resource<List<Movie>>?> = _movies init { viewModelScope.launch { _movies.value = Resource.Loading _movies.value = repository.getPopularMovies() } } @AssistedFactory interface Factory { fun create(movieId: Int) : MainViewModel } companion object { fun provideMainViewModelFactory(factory: Factory, movieId: Int) : ViewModelProvider.Factory { return object: ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return factory.create(movieId) as T } } } } } |
In the above code you can see many changes. Let’s try to understand the changes one by one.
- @AssistedInject constructor(... : Whenever you have some parameters that needs to be injected manually (or dynamically) you need to use this annotation instead of the normal @Inject annotation for the constructor. Also note that we have removed the @HiltViewModel annotation. As this time we are going to use a factory to build the ViewModel instance.
- @Assisted private val movieId: Int : Now for all the parameters that needs to be passed dynamically we will use @Assisted annotation.
1 2 3 4 5 6 |
@AssistedFactory interface Factory { fun create(movieId: Int) : MainViewModel } |
- A factory interface is needed, that we will mark with @AssistedFactory annotation, inside this interface we need a function that will take all the dynamic parameters required. In this case we have only one parameter. This function will return the actual ViewModel.
1 2 3 4 5 6 7 8 9 10 11 |
companion object { fun provideMainViewModelFactory(factory: Factory, movieId: Int) : ViewModelProvider.Factory { return object: ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return factory.create(movieId) as T } } } } |
- Inside the companion object we have a function that is returning ViewModelProvider.Factory . Inside this factory we have the overriden function create() and this function is creating the ViewModel using the factory that we passed as a parameter; we also passed movieId .
- Now we can inject the AssistedFactory in our Activity/Fragment, and then we can call the provideMainViewModelFactory()Â function to get the factory that will create the ViewModel instance. Sounds confusing? Let’s see how to do it.
Creating ViewModel with Assisted Injection
Now let’s come to the MainActivity and here we will initialize the MainViewModel. We can use the following code to create the ViewModel.
1 2 3 4 5 6 7 |
@Inject lateinit var factory: MainViewModel.Factory private val viewModel: MainViewModel by viewModels { MainViewModel.provideMainViewModelFactory(factory, 1) } |
Pretty simple right? We can use the @Inject to inject the @AssistedFactory inside our Activity/Fragment, but we cannot do it inside a composable function.
To get the factory inside a composable, we need to create an EntryPoint.
Creating an EntryPoint
We will create one more interface named ViewModelFactoryProvider .
1 2 3 4 5 6 7 |
@EntryPoint @InstallIn(ActivityComponent::class) interface ViewModelFactoryProvider { fun mainViewModelFactory(): MainViewModel.Factory } |
The function inside this interface will return the AssistedFactory that we defined inside the ViewModel. Now we can use this function to get the factory inside a composable. And with the help of this factory we can create the ViewModel.
1 2 3 4 5 6 7 8 |
val factory = EntryPointAccessors.fromActivity( LocalContext.current as Activity, ViewModelFactoryProvider::class.java ).mainViewModelFactory() val viewModel: MainViewModel = viewModel(factory = MainViewModel.provideMainViewModelFactory(factory, 1)) |
And this is how we can create the viewModel instance inside a composable function.
I hope you found this tutorial helpful and learned something. If you have any confusion regarding Assisted Injection in ViewModel then please leave your comments below. You can also watch the video to understand Assisted Injection in ViewModel in more detail.
Please share this post with your friends, if you think it is helpful. Thank You 🙂