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?
Assisted injection is now supported by Dagger and InflationInjection got its own repo. The syntax would now be like :
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
...
class ItemViewModel
@AssistedInject
constructor(private val repo: ItemRepository, @Assisted private val itemId: Long) : ViewModel() {
init {
repo.itemId = itemId
}
//[email protected]
@AssistedFactory
interface AssistedFactory {
fun create(@Named("item_id") 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)
}
}
Base on Redek answer. More on that here
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)
}
}
There is a better solution without AssistedInject, just use the SavedStateHandle.
if the args in your fragment contains userId:
val args: UserFragmentArgs by navArgs()
...
args.userId
Then simply in your savedStateHandle it will be available without additional work.
class UserViewModel @Inject constructor (
private val state: SavedStateHandle
) : ViewModel() {
//Get the value of the userId here
val userId = state.get<String>("userId")
//Also you can do this using safe args
val args = UserFragmentArgs.fromSavedStateHandle(state)
val userId = args.userId
}
Full Implementation details: https://mattrobertson.dev/passing-safe-args-to-your-viewmodel-with-hilt-366762ff3f57
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With