I am first trying android ViewModel
and Hilt
DI
As i understand from below link, to initialize ViewModel with a value on run-time i should use ViewModelFactory
//ViewModel class ScoreViewModel(finalScore: Int) : ViewModel() { // The final score var score = finalScore init { Log.i("ScoreViewModel", "Final score is $finalScore") } } //ViewModelFactory override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) { return ScoreViewModel(finalScore) as T } throw IllegalArgumentException("Unknown ViewModel class") } //Fragment viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
And to use ViewModel with hilt i should use @ViewModelInject
as explained in below link
//ViewModel class ExampleViewModel @ViewModelInject constructor( private val repository: ExampleRepository, @Assisted private val savedStateHandle: SavedStateHandle ) : ViewModel() { ... } //Activity / Fragment @AndroidEntryPoint class ExampleActivity : AppCompatActivity() { private val exampleViewModel: ExampleViewModel by viewModels() ... }
But how to use Hilt
with ViewModelFactory
?
It seems the answer is in the @Assisted
but i can’t figure out how
How to tell hilt i like it to inject repository interfaces to ViewModel while still allowing ViewModelFactory to initialize the ViewModel with parameters on run-time?
Answer
courtesy of @Elye, next articles helped a lot. I recommend a read.
Passing Activity Intent Data to ViewModel through Injection
Injecting ViewModel with Dagger Hilt
It seems that mostly Factory is not needed since mostly viewmodel
initial parameters are taken from previous fragment and can be accessed via SavedStateHandle
which is automatically injected if marked as @Assisted
To set-up hilt i used the following code-labs tutorial
Using Hilt in your Android app
Then, viewModel
injection is done automatically using only the next code
Note that as noted by fabioCollini here, it seems savedStateHandle
can also get values from safe args by simply placing argument name as the key. In fact, that is what i did in the following example. ps: In an attempt to make the safe args be more “safe”, i did try to replace the SavedStateHandle
with ItemsFragmentArgs
hoping it will work but the app did not compile. I do hope it will be implemented in the future (and if already, please let me know)
//ItemFragment file @AndroidEntryPoint class ItemsFragment : Fragment() { private val viewModel: ItemsViewModel by viewModels() //use viewModel as you would. No need to initialize. } //Module file - if you have any repository, remember to bind it //or provide the exact implementation as noted in code-labs @InstallIn(ApplicationComponent::class) @Module abstract class DatabaseModuleBinder { @Binds abstract fun bindGlistRepository(impl: FirestoreGlistRepository): GlistRepository } //ItemsViewModel file - lastly, anotate as follows and take your arguments //from savedStateHandle (for safe args, use variable name as key) class ItemsViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository, @Assisted private val savedStateHandle: SavedStateHandle) : ViewModel() { private val glistLiveDate = glistRepo.getGlistLiveData( savedStateHandle.get<String>("listId")!! ) .. }
Hope it helps anyone and if any mistake, please let me know