Context
Итак, я работал с архитектурой MVVM только для нескольких проектов. Я все еще пытаюсь понять и улучшить работу архитектуры. Я всегда работал с архитектурой MVP, используя обычный набор инструментов, Dagger для DI, обычно многомодульные проекты, слой Presenter внедряется с помощью набора Interactors / UseCases, и каждый Interactor вводится с различными репозиториями для выполнения вызовов API бэкэнда. .
Теперь, когда я перешел в MVVM, я изменил слой Presenter с помощью ViewModel, связь с ViewModel на уровень UI осуществляется через LiveData вместо использования интерфейса обратного вызова View и т. Д.
выглядит так:
class ProductDetailViewModel @inject constructor(
private val getProductsUseCase: GetProductsUseCase,
private val getUserInfoUseCase: GetUserInfoUseCase,
) : ViewModel(), GetProductsUseCase.Callback, GetUserInfoUseCase.Callback {
// Sealed class used to represent the state of the ViewModel
sealed class ProductDetailViewState {
data class UserInfoFetched(
val userInfo: UserInfo
) : ProductDetailViewState(),
data class ProductListFetched(
val products: List<Product>
) : ProductDetailViewState(),
object ErrorFetchingInfo : ProductDetailViewState()
object LoadingInfo : ProductDetailViewState()
}
...
// Live data to communicate back with the UI layer
val state = MutableLiveData<ProductDetailViewState>()
...
// region Implementation of the UseCases callbacks
override fun onSuccessfullyFetchedProducts(products: List<Product>) {
state.value = ProductDetailViewState.ProductListFetched(products)
}
override fun onErrorFetchingProducts(e: Exception) {
state.value = ProductDetailViewState.ErrorFetchingInfo
}
override fun onSuccessfullyFetchedUserInfo(userInfo: UserInfo) {
state.value = ProductDetailViewState.UserInfoFetched(userInfo)
}
override fun onErrorFetchingUserInfo(e: Exception) {
state.value = ProductDetailViewState.ErrorFetchingInfo
}
// Functions to call the UseCases from the UI layer
fun fetchUserProductInfo() {
state.value = ProductDetailViewState.LoadingInfo
getProductsUseCase.execute(this)
getUserInfoUseCase.execute(this)
}
}
Здесь нет науки о ракетах, иногда я меняю реализацию, чтобы использовать более одного свойства LiveData для отслеживания изменений. Кстати, это всего лишь пример, который я написал на лету, поэтому не ожидайте, что он скомпилируется. Но это просто так, ViewModel внедряется с кучей UseCases, он реализует интерфейсы обратного вызова UseCases и когда я получаю результаты из UseCases, я передаю это на уровень пользовательского интерфейса через LiveData.
Мои варианты использования обычно выглядят так:
// UseCase interface
interface GetProductsUseCase {
interface Callback {
fun onSuccessfullyFetchedProducts(products: List<Product>)
fun onErrorFetchingProducts(e: Exception)
}
fun execute(callback: Callback)
}
// Actual implementation
class GetProductsUseCaseImpl(
private val productRepository: ApiProductRepostory
) : GetProductsUseCase {
override fun execute(callback: Callback) {
productRepository.fetchProducts() // Fetches the products from the backend through Retrofit
.subscribe(
{
// onNext()
callback.onSuccessfullyFetchedProducts(it)
},
{
// onError()
callback.onErrorFetchingProducts(it)
}
)
}
}
Мои классы репозитория обычно являются обертками для экземпляра Retrofit, и они заботятся о настройке правильного планировщика, чтобы все работало в правильном потоке и отображали ответы бэкэнда в классы модели. Под ответами бэкэнда я подразумеваю классы, сопоставленные с Gson (например,
список ApiProductResponse), и они отображаются на классы моделей (например, список продуктов, которые я использую в приложении)
Вопрос
Мой вопрос здесь заключается в том, что, так как я начал работать с архитектурой MVVM со всеми статьями и всеми примерами, люди либо вводят репозитории прямо в ViewModel (дублирующий код для обработки ошибок и сопоставления ответов), либо либо используют Single Образец Истины (получение информации из комнаты с использованием комнатных комнат). Но я не видел, чтобы кто-нибудь использовал UseCases со слоем ViewModel. Я имею в виду, что это довольно удобно, я делаю вещи разделенными, я делаю отображение ответов бэкэнда в UseCases, я обрабатываю любую ошибку там. Но, тем не менее, похоже, что я не вижу, чтобы кто-то делал это, Есть ли способ улучшить UseCases, чтобы сделать их более удобными для ViewModels с точки зрения API? Выполнить связь между UseCases и ViewModels с помощью чего-то еще, кроме интерфейса обратного вызова?
Пожалуйста, дайте мне знать, если вам нужна дополнительная информация об этом. Извините за примеры, я знаю, что они не самые лучшие, я просто предложил кое-что простое, чтобы объяснить это лучше.
Спасибо
Редактировать # 1
Вот так выглядят мои классы репозитория:
// ApiProductRepository interface
interface ApiProductRepository {
fun fetchProducts(): Single<NetworkResponse<List<ApiProductResponse>>>
}
// Actual implementation
class ApiProductRepositoryImpl(
private val retrofitApi: ApiProducts, // This is a Retrofit API interface
private val uiScheduler: Scheduler, // AndroidSchedulers.mainThread()
private val backgroundScheduler: Scheduler, // Schedulers.io()
) : GetProductsUseCase {
override fun fetchProducts(): Single<NetworkResponse<List<ApiProductResponse>>> {
return retrofitApi.fetchProducts() // Does the API call using the Retrofit interface. I've the RxAdapter set.
.wrapOnNetworkResponse() // Extended function that converts the Retrofit's Response object into a NetworkResponse class
.observeOn(uiScheduler)
.subscribeOn(backgroundScheduler)
}
}
// The network response class is a class that just carries the Retrofit's Response class status code