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
Use a 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
Hilt and Jetpack integrations
//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?
Inject ViewModel objects with HiltProvide a ViewModel by annotating it with @HiltViewModel and using the @Inject annotation in the ViewModel object's constructor. Note: To use Dagger's assisted injection with ViewModels, see the following Github issue.
Annotation Type HiltViewModelIdentifies a ViewModel for construction injection. The ViewModel annotated with HiltViewModel will be available for creation by the dagger. hilt. android. lifecycle.
Hilt provides a standard way to incorporate Dagger dependency injection into an Android application. The goals of Hilt are: To simplify Dagger-related infrastructure for Android apps. To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.
Jetpack Compose is Android's new modern toolkit for building native UI based declarative component approaches. In contrast to imperative programming, declarative programming is a programming paradigm that expresses the logic of computation or drawing UIs without describing its control flow.
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
Pass your ScoreViewModelFactory into built in ktx-extension of viewModel. Also you can consume Activity/Fragment arguments by using SavedStateHandle itself with defaultViewModelProviderFactory.
/*
Gradle Dependencies
def lifecycle_version = "2.2.0"
def hiltLifeVersion = "1.0.0-alpha01"
def hiltVersion = "2.28.1-alpha"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "com.google.dagger:hilt-android:$hiltVersion"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01"
implementation "androidx.hilt:hilt-work:$hiltLifeVersion"
implementation "androidx.hilt:hilt-common:1.0.0-alpha01"
kapt "com.google.dagger:hilt-android-compiler:$hiltVersion"
kapt "androidx.hilt:hilt-compiler:$hiltLifeVersion"
*/
import androidx.fragment.app.viewModels
@AndroidEntryPoint
class ExampleFragment : Fragment(R.layout.example_fragment) {
//internally using defaultViewModelProviderFactory
private val viewModel : ExampleViewModel by viewModels()
//or you own viewmodal factory instance --> scoreViewModelFactory
private val viewModel : ExampleViewModel by viewModels { scoreViewModelFactory }
}
class ExampleViewModel @ViewModelInject constructor(
private val repository: ExampleRepository,
@Assisted override val savedStateHandle: SavedStateHandle
) : ViewModel() {
//bundle args -> String, Int, Parcelable etc..
private val arg1LiveData: MutableLiveData<String> =
savedStateHandle.getLiveData("arg1", "")
}
In built ktx-extension of Fragment viewmodel
@MainThread
inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
) = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
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