Assisted Dependency Injection in ViewModel with Dagger-Hilt in Android
If you use Dagger-Hilt Dependency Injection in your android project then you must have encountered a case where you want to manually supply some parameters to construct an object in order to inject it. Generally when we inject a dependency/object in a class then the Dagger-Hilt framework construct it behind the scenes by automatically passing specified parameters to it but what when dagger-hilt doesn’t know about some dependencies that how to inject them & we want to manually supply those dependencies at runtime by our own.
Example: When we create a ViewModel object then in viewmodel’s constructor we specified required dependencies (it might require your network repository object & your local database/DAO object, etc) to be injected by dagger-hilt in order to construct it. Now let’s assume while dagger-hilt is injecting required specified dependencies in viewmodel, you also want to manually pass some object/dependency at runtime in viewmodel to construct it… So, How would you do that ?
So, in this article, we will see how we can manually assist some dependencies at runtime along with dagger-hilt supplied dependencies. If you want to see how to set up/use Dagger-Hilt dependency injection then please first refer to the above tagged Dagger-Hilt article.
Understand How to use Assisted Injection in case of ViewModel with an Example
Suppose we have ArticlesFeedViewModel which is to be injected in ArticlesFeedFragment. Now suppose as this fragment will appear on the screen you need to fetch the list of all the published articles by you & have to show them into UI and to make that api call, we are calling getPublishedArticles() function present in ArticlesFeedViewModel’s init{ } block so that api gets called as soon as this viewmodel gets initialized. Now ArticlesFeedViewModel’s getPublishedArticles() will further call a function present in ArticlesRepository to start making a network call, So we need a ArticlesRepository object too in this viewmodel & we are injecting ArticlesRepository in viewmodel’s constructor with the help of Dagger-Hilt.
But have you noticed that we are also having one more object/dependency named userId annotated with @Assisted? Actually we need to pass that userId at runtime in getPublishedArticles() function in order to fetch articles published by you & we are getting this userId in ArticleViewModel from ArticlesFeedFragment as soon as viewmodel gets instantiated in that fragment. But how ? we will see this in while, let’s first better understand the code inside viewmodel.
Understanding ViewModel’s code
Compare to normal injection, We need to make a few changes in the case of Assisted Injection:
- We use @AssistedInject instead of @Inject for constructor injection.
- Arguments which need to be provided at runtime are annotated with @Assisted.
Now, after doing the above changes still we can’t directly inject our viewmodel in fragment/activity, we first need to create a factory for it because we will use that factory to create an instance of the actual class i.e. viewmodel in our case. (please note that we create a factory for our class/viewmodel as an interface)
So, let’s talk about factory now:
- We annotate our factory with @AssistedFactory. This annotation tells the Dagger-Hilt that this interface is used to create an instance of a class/viewmodel that requires Assisted Injection.
- Inside this factory, we create a function named “create”that will be responsible for returning an instance of our class i.e. ArticlesViewModel and accepts only those arguments which are to be provided/assisted by us at runtime (i.e. userId) that means arguments annotated as @Assisted.
Now we will create a factory provider function named providesFactory() which will provide ViewModelProvider.Factory and we will have anonymous class inside it that will again have overridden create a function which we have implemented to actually create an instance of our viewmodel. Note that providesFactory() function will require assistedFactory & all the assisted arguments to be supplied at runtime.
Kotlin
class ArticlesFeedViewModel @AssistedInject constructor(
private val articlesRepo: ArticlesRepository,
@Assisted
private val userId: String
) : ViewModel {
@AssistedFactory
interface ArticlesFeedViewModelFactory {
fun create(userId: String): ArticlesFeedViewModel
}
@Suppress ( "UNCHECKED_CAST" )
companion object {
fun providesFactory(
assistedFactory: ArticlesFeedViewModelFactory,
userId: String
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return assistedFactory.create(userId) as T
}
}
}
init {
getPublishedArticles(userId)
}
private fun getPublishedArticles(userId: String) {
val response = articlesRepo.getPublishedArticles(userId).cachedIn(viewModelScope)
}
|
Injecting Viewmodel’s Factory & viewmodel itself
We are using Jetpack Navigation Component in this example for fragments navigation. Now let’s assume we are navigating to ArticlesFeedFragment from let’s say HomeFragment & we are sending userId from HomeFragment to ArticlesFeedFragment by using Safe Args.
So, first we have injected our ArticlesViewModel’s factory. Now we will instantiate our viewmodel with the help of kotlin delegate property “by ” and then we’ll call ArticlesFeedViewModel.providesFactory() fun inside it that will require assisted factory i.e. our above-injected ArticlesFeedViewModelFactory and manually assisted userId which we are getting with help of safe args.
Kotlin
@AndroidEntryPoint
class ArticlesFeedFragment : Fragment() {
private var _binding: FragmentArticlesFeedBinding? = null
private val binding get() = _binding!!
private val args: ArticlesFeedFragmentArgs by navArgs()
@Inject
lateinit var articlesFeedViewModelFactory: ArticlesFeedViewModel.ArticlesFeedViewModelFactory
private val viewModel: ArticlesFeedViewModel by viewModels {
ArticlesFeedViewModel.providesFactory(
assistedFactory = articlesFeedViewModelFactory,
userId = args.userId
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentArticlesFeedBinding.inflate(inflater, container, false )
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super .onViewCreated(view, savedInstanceState)
}
|
So, this way we’ll get our ViewModel injected with assisted dependency userId supplied at runtime. That’s how we use Assisted Dependency Injection with Dagger-Hilt that allows us to pass arguments at runtime to construct an object/dependency.
Last Updated :
17 Mar, 2022
Like Article
Save Article
Share your thoughts in the comments
Please Login to comment...