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
, AlertManager
etc 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.
The koin-android Gradle module introduces a new viewModel DSL keyword that comes in complement of single and factory , to help declare a ViewModel component and bind it to an Android Component lifecycle. Your declared component must at least extends the android.
It is technically possible to have one ViewModel for all Fragment s. However, since this one ViewModel would have to manage a number of very different use cases, it would be something like a god object. With 20 Fragments, it would have very many lines of code ...
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.
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