I have a word quiz app where there are different word games (multiple choice, spelling, match the words, etc). I have created a ParentViewModel to keep the common things, such as getting words from Room database, current question variable, etc. But, on the other hand, as each game has different aspects. They have their own viewmodels. I want to be able to use functions and variables from ParentViewModel in child view models. My question is if I am correctly injecting the ParentViewModel below or if there is another way to do this?
ParentViewModel:
#HiltViewModel
class ParentViewModel #Inject constructor(
private val wordRepository: WordRepository
): ViewModel(){
// Common things such as getting words from database.
}
Child view model (Different view model for each type of game):
#HiltViewModel
class SpellingViewModel #Inject constructor(
practiceViewModel: PracticeViewModel
): ViewModel(){
// Functions and varibles spesific to the current game.
}
Appmodule:
#Provides
#Singleton
fun provideParentViewModel(wordRepository: WordRepository): ParentViewModel {
return ParentViewModel(wordRepository)
}
I think a baseViewModel should be created and inherit child classes from the parent class, You can create open functions that are in the parent class and use them in the child classes.
Let me give you an example :
abstract class BaseViewModel : ViewModel(){open fun wordGameOne(){
// do something
}
open fun wordGameTwo(){
// do something
}
}
And in the child class, you can override
#HiltViewModel class GameOneViewModel #Inject constructor(
private val wordRepository: WordRepository) : BaseViewModel() {
override fun wordGameOne(){
// do something
}
}
Related
I have a tab view. Ideally, I want both tabs to use the same view model type but a different instance with different repositories. Here's what that view model looks like:
#HiltViewModel
class MyViewModel #Inject constructor(
private val myRepository: MyRepository,
) : ViewModel() {
...
}
In compose, I'd want to do something like this:
if (selectedTabIndex == 0) {
val viewModel1 = hiltViewModel<MyViewModel>()
} else {
val viewModel2 = hiltViewModel<MyViewModel>()
}
Is it possible to inject the right repository, then choose the right instance for viewModel1 and viewModel2?
I could just make different view model types (eg MyViewModel1 and MyViewModel2) and use #Named for the MyRepository dependency but I wanted to avoid this since otherwise the view models are identical.
I'm trying to inject a singleton class that was defined in a hiltmodule inside a composable.
I know how to inject viewmodels but what about singleton classes ?
#Inject
lateinit var mysingleton: MySingletonClass
This code works fine in an activity but carrying it around from the activity to the composable that uses it is a long way ...
Any better solution ?
You cannot inject dependencies into a function, which is what a #Composable is. #Composable functions don't have dependencies, but can get values returned by Hilt functions, like hiltViewModel().
If you need access to a ViewModel-scoped (or Application-scoped) singleton inside a #Composable, you can have that singleton injected into the ViewModel, and then access the ViewModel from the #Composable.
You can inject that singleton into the ViewModel by annotating the provider function for that object in the ViewModel hilt module as #ViewScoped.
You could install the provider into the SingletonComponent::class and annotate it as #Singleton, if you want a singleton for the whole app, instead of a singleton per ViewModel instance. More info here.
Hilt module file
#Module
#InstallIn(ViewModelComponent::class)
object ViewModelModule {
#ViewScoped
#Provides
fun provideMySingleton(): MySingletonClass = MySingletonClass()
}
Your ViewModel class:
#HiltViewModel
class MyViewModel
#Inject constructor(
val mySingleton: MySingletonClass
): ViewModel() {
...
}
Your #Composable function:
#Composable fun DisplayPrettyScreen() {
...
val viewModel: MyViewModel = hiltViewModel()
val singleton = viewModel.mySingleton //no need to assign it to a local variable, just for explanation purposes
}
I also thought that is not possible but then found way... tried it and seems it works.
define you entry point interface:
private lateinit var dataStoreEntryPoint: DataStoreEntryPoint
#Composable
fun requireDataStoreEntryPoint(): DataStoreEntryPoint {
if (!::dataStoreEntryPoint.isInitialized) {
dataStoreEntryPoint =
EntryPoints.get(
LocalContext.current.applicationContext,
DataStoreEntryPoint::class.java,
)
}
return dataStoreEntryPoint
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DataStoreEntryPoint {
val dataStoreRepo: DataStoreRepo
}
DataStoreRepo is singleton defined in Hilt
#Singleton
#Provides
fun provideDataStoreRepository(dataStore: DataStore<Preferences>): DataStoreRepo =
DataStoreRepo(dataStore)
and then use in composable:
#Composable
fun ComposableFuncionName(dataStoreRepo: DataStoreRepo = requireDataStoreEntryPoint().dataStoreRepo){
...
}
I have three class needing to share a dependency. The latter is initialisated by one of them.The SettingsViewModel contains the data to initialize the dependency and it need to be deleted at the end of the activity. NetworkViewModel and TimeViewModel use it as an interface since the dependancy is an interface with the logic to handle Bluetooth.
SettingsViewModel -->(initialize) SingletonDependency.
NetworkViewModel --> (use).
TimeViewModel --> (use).
How can I make Hilt (or manual) injection to use the same interface? If I understand well I can't use singleton here since I need to iniatilize the dependency when the activity start.
If we consider that your class name is SomeClass you can provide a live data of this class like this:
#Module
#InstallIn(SingletonComponent::class)
object SingeltonModule {
#Provides
#Singleton
fun provideSomeClassLiveData(): MutableLiveData<SomeClass> {
return MutableLiveData<SomeClass>()
}
}
in your SettingsViewModel do this:
#HiltViewModel
class SettingsViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.value = SomeClass()
}
}
and in other view models you can inject this to contractors and observe it:
#HiltViewModel
class NetworkViewModel #Inject constructor(
val SomeClassLiveData: MutableLiveData<SomeClass>
) : ViewModel() {
init{
someClassLiveData.observeForEver{
//do what you want with value
}
}
}
For ViewModels which has only compile-time dependencies, I use the ViewModelProvider.Factory from Architecture components like following:
class ViewModelFactory<T : ViewModel> #Inject constructor(private val viewModel: Lazy<T>) : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}
And in my Activity or Fragment I get the ViewModel in following way:
#Inject
lateinit var viewModelFactory: ViewModelFactory<ProductsViewModel>
This is working fine until my ViewModel needs a dependency which is only available at run-time.
Scenario is, I have a list of Product which I am displaying in RecyclerView. For each Product, I have ProductViewModel.
Now, the ProductViewModel needs variety of dependencies like ResourceProvider, AlertManageretc which are available compile-time and I can either Inject them using constructor or I can Provide them using Module. But, along with above dependencies, it needs Product object as well which is only available at run-time as I fetch the list of products via API call.
I don't know how to inject a dependency which is only available at run-time. So I am doing following at the moment:
ProductsFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
productsAdapter = ProductsAdapter(context!!, products, R.layout.list_item_products, BR.productVm)
rvProducts.layoutManager = LinearLayoutManager(context)
rvProducts.addItemDecoration(RecyclerViewMargin(context, 10, 20))
rvProducts.adapter = productsAdapter
getProducts()
}
private fun getProducts() {
productsViewModel.getProducts()
.observe(this, Observer { productResponse: GetProductResponse ->
products.clear()
productsAdapter?.notifyDataSetChanged()
val productsViewModels = productResponse.data.map { product ->
// Here product is fetched run-time and alertManager etc are
// injected into Fragment as they are available compile-time. I
// don't think this is correct approach and I want to get the
// ProductViewModel using Dagger only.
ProductViewModel(product, resourceProvider,
appUtils, alertManager)
}
products.addAll(productsViewModels)
productsAdapter?.notifyDataSetChanged()
})
}
ProductsAdapter binds the ProductViewModel with the list_item_products layout.
As I mentioned in comments in the code, I don't want to create ProductViewModel my self and instead I want it from dagger only. I also believe the correct approach would be to Inject the ProductsAdapter directly into the Fragment, but then also, I need to tell dagger from where it can get Product object for ProductViewModel which is available at run time and it ends up on same question for me.
Any guide or directions to achieve this would be really great.
You are on the right direction in wanting to inject dependencies instead of creating them like you are doing with ProductViewModel. But, yes, you can't inject ProductViewModel as it needs a Product which is only available a runtime.
The solution to this problem is to create a Factory of ProductViewModel:
class ProductViewModel(
val product: Product,
val resourceProvider: ResourceProvider,
val appUtils: AppUtils,
val alertManager: AlertManager
) {
// ...
}
class ProductViewModelFactory #Inject constructor(
val resourceProvider: ResourceProvider,
val appUtils: AppUtils,
val alertManager: AlertManager
) {
fun create(product: Product): ProductViewModel {
return ProductViewModel(product, resourceProvider, appUtils, alertManager)
}
}
Then inject ProductViewModelFactory in your ProductsFragment class, and call productViewModelFactory.create(product) when the Product is available.
As your project start getting bigger and you see this pattern repeating, consider using AssistedInject to reduce the boilerplate.
I have SetupActivity and there in it’s viewModel I fetch data and then that data I need to pass it to MainActivity and it’s Fragment’s ViewModels , I am wondering instead of passing it is it possible to inject dynamically that settingsData in ViewModel's constructor's
settingsViewModel.settingsMutableData.observe(this, Observer {settingsData->
startActivity(MainActivity.getStartInent(this,settingsData))
})
#IntoMap
#ViewModelKey(PageViewModel::class)
abstract fun bindHomeViewModel(pageViewModel: PageViewModel): ViewModel```
Here is one of my ViewModel
```class PageViewModel #Inject constructor( homeRepository: HomeRepository) : ViewModel(), Injectable {
}```