Dagger2.10+: внедрить ViewModel во фрагмент/активность, которая имеет зависимости во время выполнения.

Для ViewModels, который имеет только зависимости времени компиляции, я использую компоненты ViewModelProvider.Factory from Architecture, как показано ниже:

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
}

И в моем Activity или Fragment я получаю ViewModel следующим образом:

@Inject
lateinit var viewModelFactory: ViewModelFactory<ProductsViewModel>

Это работает нормально, пока моему ViewModel не понадобится зависимость, которая доступна только во время времени выполнения.

Сценарий таков: у меня есть список Product, который я показываю в RecyclerView. Для каждого Product у меня есть ProductViewModel.

Теперь для ProductViewModel нужны различные зависимости, такие как ResourceProvider, AlertManagerи т. д., которые доступны во время компиляции, и я могу либо Inject их использовать с помощью constructor, либо Provide с помощью Module. Но, наряду с указанными выше зависимостями, ему также нужен объект Product, который доступен только во время выполнения, когда я получаю список продуктов через вызов API.

Я не знаю, как внедрить зависимость, которая доступна только во время выполнения. Итак, я делаю следующее на данный момент:

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 связывает ProductViewModel с раскладкой list_item_products.

Как я упоминал в комментариях к коду, я не хочу создавать ProductViewModel сам, а вместо этого хочу, чтобы это было только из кинжала. Я также считаю, что правильным подходом было бы внедрить ProductsAdapter непосредственно в Fragment, но также мне нужно указать dagger, откуда он может получить объект Product для ProductViewModel, который доступен во время выполнения, и он заканчивается тем же вопросом для меня.

Любое руководство или указания для достижения этого было бы действительно здорово.


person Sandip Fichadiya    schedule 02.09.2019    source источник
comment
Вы пробовали AssistedInject? Я думаю, это то, что вы ищете. У меня также есть эта статья в случае, если вы хотите знать, как его использовать.   -  person coroutineDispatcher    schedule 04.09.2019
comment
дубликат 1 2   -  person denvercoder9    schedule 04.09.2019
comment
@coroutineDispatcher Спасибо за ссылку, быстро проверю :)   -  person Sandip Fichadiya    schedule 04.09.2019
comment
@ rafid059 Я не знал о термине AssistedInject и, следовательно, не мог найти решение этой проблемы :)   -  person Sandip Fichadiya    schedule 04.09.2019


Ответы (1)


Вы находитесь в правильном направлении, желая внедрить зависимости, а не создавать их, как вы делаете с ProductViewModel. Но да, вы не можете внедрить ProductViewModel, так как ему нужен продукт, который доступен только во время выполнения.

Решение этой проблемы заключается в создании фабрики 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)
    }  
}

Затем введите ProductViewModelFactory в свой класс ProductsFragment и вызовите productViewModelFactory.create(product), когда продукт будет доступен.


По мере того, как ваш проект становится больше и вы видите, что этот шаблон повторяется, рассмотрите возможность использования AssistedInject, чтобы уменьшить шаблон.

person Gustavo    schedule 04.09.2019
comment
Похоже, путь. Спасибо, я проверю это, внедрив в ближайшее время :) - person Sandip Fichadiya; 04.09.2019