Search code examples
androidandroid-fragmentsdependency-injectionandroid-viewmodeldagger-hilt

How to pass runtime parameters to a ViewModel's constructor when using Hilt for dependency injection?


I'm wondering how to pass runtime parameters to a ViewModel's constructor while using Hilt for DI? Prior to using Hilt, I have a ViewModel that looks like this:

class ItemViewModel(private val itemId: Long) : ViewModel() {
    private val repo = ItemRepository(itemId) 
}

class ItemViewModelFactory(private val itemId: Long) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
    if (modelClass.isAssignableFrom(ItemViewModel::class.java)) {
        return ItemViewModel(itemId) as T
    }
    throw IllegalArgumentException("Unknown ViewModel class")
}

I create the above ViewModel in my fragment like this:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

    val args: ItemScreenFragmentArgs by navArgs()
    val itemId = args.itemId

    //Create the view model factory
    val viewModelFactory = ItemViewModelFactory(application, itemId)

    // Get a reference to the ViewModel associated with this fragment.
    val itemViewModel = ViewModelProvider(this, viewModelFactory).get(ItemViewModel::class.java)
}

If my ItemViewModel constructor didn't have the itemId parameter, my ViewModel and Fragment using Hilt would look like this:

class ItemViewModel
@ViewModelInject
constructor(private val repo: ItemRepository) : ViewModel() { }

@AndroidEntryPoint
class ItemFragment : Fragment() {
    private val itemViewModel: ItemViewModel by viewModels ()
}

I'm trying to figure out how to pass the itemId that I get from the ItemFragment's NavArgs to the ItemViewModel's constructor? Is there a way to do this with Hilt?


Solution

  • For anyone else looking to pass runtime parameters to a ViewModel while using Dagger Hilt, this is how I did it:

    I followed the code from this example which uses the AssistedInject library.

    My code now looks as follows:

    class ItemViewModel
    @AssistedInject
    constructor(private val repo: ItemRepository, @Assisted private val itemId: Long) : ViewModel() {
        init {
            repo.itemId = itemId
        }
    
        @AssistedInject.Factory
        interface AssistedFactory {
            fun create(itemId: Long): ItemViewModel
        }
    
        companion object {
            fun provideFactory(
                assistedFactory: AssistedFactory,
                itemId: Long
            ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
                override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                    return assistedFactory.create(itemId) as T
                }
            }
        }
    }
    
    @InstallIn(FragmentComponent::class)
    @AssistedModule
    @Module
    interface AssistedInjectModule {}
    
    @AndroidEntryPoint
    class ItemFragment : Fragment() {
        private val args: ItemScreenFragmentArgs by navArgs()      
        @Inject lateinit var itemViewModelAssistedFactory: ItemViewModel.AssistedFactory        
        private val itemViewModel: ItemViewModel by viewModels {
                ItemViewModel.provideFactory(itemViewModelAssistedFactory, args.itemId)
        }    
    }