Open In App

Assisted Dependency Injection in ViewModel with Dagger-Hilt in Android

Improve
Improve
Like Article
Like
Save
Share
Report

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




// We are using @AssistedInject instead
// of normal @Inject as we want
// to assist one dependency by our own
// at runtime and rest by dagger-hilt
class ArticlesFeedViewModel @AssistedInject constructor(
    private val articlesRepo: ArticlesRepository,
    // dependency which is to be assisted by
    // us should be annotated with @Assisted
    @Assisted
    private val userId: String
) : ViewModel {
 
    // It's a factory of this viewmodel, we need
    // to annotate this factory interface
    // with @AssistedFactory in order to
    // let Dagger-Hilt know about it
    @AssistedFactory
    interface ArticlesFeedViewModelFactory {
        fun create(userId: String): ArticlesFeedViewModel
    }
 
    // Suppressing unchecked cast warning
    @Suppress("UNCHECKED_CAST")
    companion object {
       
        // putting this function inside
        // companion object so that we can 
        // access it from outside i.e. from fragment/activity
        fun providesFactory(
            assistedFactory: ArticlesFeedViewModelFactory,
            userId: String
        ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                 
                // using our ArticlesFeedViewModelFactory's create function
                // to actually create our viewmodel instance
                return assistedFactory.create(userId) as T
            }
        }
    }
 
    init {
       // Calling getPublishedArticles() function
       // in viewmodel's init block
       // because init block just gets executed
       // after primary constructor
       // So, actually this api will get called as soon
       // as viewmodel gets instantiated
       getPublishedArticles(userId)
    }
 
    private fun getPublishedArticles(userId: String) {
        // Just for sack of example we are just
        // simply storing response in a variable
        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() {
 
    // using viewbinding
    private var _binding: FragmentArticlesFeedBinding? = null
    private val binding get() = _binding!!
     
    // Using navargs for getting argument
    // supplied from previous fragment
    private val args: ArticlesFeedFragmentArgs by navArgs()
 
    // First injecting our
    // viewmodel's factory interface
    @Inject
    lateinit var articlesFeedViewModelFactory: ArticlesFeedViewModel.ArticlesFeedViewModelFactory
 
    // Creating viewmodel here with
    // the help of kotlin delegate property "by"
    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)
         
        // Do something here
        // You can observe/handle your api response
       // here coming through ArticlesViewModel
    }


 

 

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
Previous
Next
Share your thoughts in the comments
Similar Reads