具有交互器/用例的 MVVM 架构

4gu*_*71n 6 android mvvm android-livedata android-viewmodel

语境

所以,我一直在为几个项目使用 MVVM 架构。我仍在努力弄清楚并改进架构的工作方式。我总是使用 MVP 架构,使用常用的工具集,Dagger for DI,通常是多模块项目,Presenter 层被注入了一堆 Interactor/UseCases,每个 Interactor 被注入了不同的 Repositories 来执行后端 API 调用.

现在我已经进入 MVVM,我通过 ViewModel 更改了 Presenter 层,从 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这里没有火箭科学,有时我会更改实现以使用多个 LiveData 属性来跟踪更改。顺便说一下,这只是我即时编写的一个示例,所以不要指望它会编译。但仅此而已,ViewModel 注入了一堆 UseCases,它实现了 UseCases 回调接口,当我从 UseCases 获得结果时,我通过 LiveData 将其传达给 UI 层。

我的用例通常是这样的:

// 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)
                }
            )
    }
}
Run Code Online (Sandbox Code Playgroud)

我的 Repository 类通常是 Retrofit 实例的包装器,它们负责设置适当的调度程序,以便一切都在适当的线程上运行,并将后端响应映射到模型类中。后端响应是指用 Gson 映射的类(例如 ApiProductResponse 列表),它们被映射到模型类(例如我在整个应用程序中使用的产品列表)

我的问题是,自从我开始使用 MVVM 架构所有文章和所有示例后,人们要么将 Repositories 直接注入 ViewModel(复制代码以处理错误并映射响应),要么使用 Single Source of Truth模式(使用 Room 的 Flowables 从 Room 获取信息)。但是我还没有看到有人使用带有 ViewModel 层的 UseCases。我的意思是它非常方便,我可以将事情分开,我在用例中映射后端响应,我处理那里的任何错误。但是,仍然觉得很可能我没有看到有人这样做,有什么方法可以改进用例,使它们在 API 方面对 ViewModel 更友好?使用回调接口以外的其他方式执行用例和视图模型之间的通信?

如果您需要有关此的更多信息,请告诉我。对不起,这些例子,我知道这些不是最好的,我只是想出了一些简单的东西来更好地解释它。

谢谢,

编辑 #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
Run Code Online (Sandbox Code Playgroud)

Iva*_*van 2

更新您的用例以使其返回Single<List<Product>>

class GetProducts @Inject constructor(private val repository: ApiProductRepository) {
    operator fun invoke(): Single<List<Product>> {
        return repository.fetchProducts()
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,更新您的ViewModel,以便它订阅产品流:

class ProductDetailViewModel @Inject constructor(
    private val getProducts: GetProducts
): ViewModel() {

    val state: LiveData<ProductDetailViewState> get() = _state
    private val _state = MutableLiveData<ProductDetailViewState>()

    private val compositeDisposable = CompositeDisposable()

    init {
        subscribeToProducts()
    }

    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear()
    }

    private fun subscribeToProducts() {
        getProducts()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.main())
            .subscribe(
                {
                    // onNext()
                    _state.value = ProductListFetched(products = it)
                },
                {
                    // onError()
                    _state.value = ErrorFetchingInfo
                }
            ).addTo(compositeDisposable)
    }

}

sealed class ProductDetailViewState {
    data class ProductListFetched(
        val products: List<Product>
    ): ProductDetailViewState()
    object ErrorFetchingInfo : ProductDetailViewState()
}
Run Code Online (Sandbox Code Playgroud)

List<ApiProductResponse>>我忽略的一件事是to的适应List<Product>,但这可以通过使用辅助函数映射列表来处理。