【问题标题】:MVVM architecture with Interactors/UseCases带有交互器/用例的 MVVM 架构
【发布时间】:2019-03-30 14:29:30
【问题描述】:

上下文

所以,我一直在为几个项目使用 MVVM 架构。我仍在尝试弄清楚并改进架构的工作方式。我一直使用 MVP 架构,使用通常的工具集,用于 DI 的 Dagger,通常是多模块项目,Presenter 层被注入一堆交互器/用例,每个交互器被注入不同的存储库以执行后端 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)
    }
}

这里没有火箭科学,有时我会更改实现以使用多个 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)
                }
            )
    }
}

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

问题

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

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

谢谢,

编辑#1

这就是我的 Repository 类的样子:

// 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

【问题讨论】:

  • 你找到正确的解决方案了吗?
  • 您找到不使用 UseCase/Interactor 的理由/案例了吗?我从 MVVM 方法开始,我不确定它们是否是不必要的层。
  • @Abbas 不,实际上我在最新的 MVVM 项目中使用了用例。唯一的区别是,我使用 RxJava,而不是使用回调将用例传递给 ViewModel。
  • @4gus71 你的意思是使用 RxJava 和 observsbles 吗?所以在 ViewModel 中我们订阅 observable 变量,将其传递给 Interactor 方法,并通过更新 Interactor 中的 observable 变量来更新 ViewModel 中的值。如果我不正确,请纠正我。
  • @IgorLevkivskiy 实际上在我的交互器上,我只有返回 Observable 的函数。在 ViewModel 我调用这个函数,订阅 observable 并做我需要做的任何事情。例如,获取订单列表的交互器将有一个函数:fun fetchOrders(): Observable&lt;List&lt;Order&gt;&gt; {... 然后在 ViewModel 上我做:fetchOrdersInteractor.fetchOrders().subscribe {....} 类似的东西。

标签: android mvvm android-livedata android-viewmodel


【解决方案1】:

更新您的用例,使其返回Single&lt;List&lt;Product&gt;&gt;

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

然后,更新您的 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()
}

我要省略的一件事是 List&lt;ApiProductResponse&gt;&gt;List&lt;Product&gt; 的适应,但这可以通过使用辅助函数映射列表来处理。

【讨论】:

  • 很好!!!我不知道你可以在这样的类上覆盖invoke,它看起来超级干净! ?
【解决方案2】:

我刚刚开始在我的最后两个项目中使用 MVVM。我可以与您分享我处理 ViewModel 中的 REST API 的过程。希望对您和其他人有所帮助。

  • 用他们的回调创建一个 Generic Retrofit Executer 类。这将接受改造调用对象并为您提供数据。
  • 为您的特定包或模块创建一个存储库,您可以在其中处理所有 API 请求。就我而言,我通过 API 的 id 获取了一个用户。 这是用户存储库。

class UserRepository {


    @Inject
    lateinit var mRetrofit: Retrofit

    init {
        MainApplication.appComponent!!.inject(this)
    }

    private val userApi = mRetrofit.create(UserApi::class.java)

    fun getUserbyId(id: Int): Single<NetworkResponse<User>> {
        return Single.create<NetworkResponse<User>>{
            emitter ->
            val callbyId = userApi.getUserbyId(id)
            GenericReqExecutor(callbyId).executeCallRequest(object : ExecutionListener<User>{
                override fun onSuccess(response: User) {
                    emitter.onSuccess(NetworkResponse(success = true,
                            response = response
                            ))
                }

                override fun onApiError(error: NetworkError) {
                    emitter.onSuccess(NetworkResponse(success = false,
                            response = User(),
                            networkError = error
                            ))
                }

                override fun onFailure(error: Throwable) {
                    emitter.onError(error)
                }

            })
        }
    }

}
  • 然后在您的 ViewModel 中使用此存储库。就我而言,这是我的 LoginViewModel 代码

 class LoginViewModel : ViewModel()  {

     var userRepo = UserRepository()

     fun getUserById(id :Int){
         var diposable = userRepo.getUserbyId(id).subscribe({

             //OnNext

         },{
             //onError
         })
     }
}

我希望这种方法可以帮助您减少一些样板代码。 谢谢

【讨论】:

  • 您好,感谢您放弃反馈。我认为我在进行 API 调用方面并不费力。我的意思是,你可以通过使用 Retrofit 的 RxAdapter 来摆脱一些样板代码。无需自己将响应包装到 Single's 中。我在帖子上添加了更多信息。
  • 另外,我有点好奇您是如何通过 LiveData 将结果传达给 UI 层的。你能添加一个简短的例子吗?
【解决方案3】:

不久前我开始使用 MVVM 时也遇到了同样的问题。我提出了以下基于 Kotlin 挂起函数和协程的解决方案:

  1. 将 ApiProductRepositoryImpl.fetchProducts() 更改为同步运行。为此,请将改造接口更改为返回 Call<...>,然后将存储库实现更改为
// error handling omitted for brevity
override fun fetchProducts() = retrofitApi.fetchProducts().execute().body()
  1. 让您的用例实现以下接口:
interface UseCase<InputType, OutputType> {
    suspend fun execute(input: InputType): OutputType
}

所以您的 GetProductsUseCase 看起来像这样:

class GetProductsUseCase: UseCase<Unit, List<Product>> {
    suspend fun execute(input: Unit): List<Product> = withContext(Dispatchers.IO){
        // withContext causes this block to run on a background thread 
        return@withContext productRepository.fetchProducts() 
}
  1. 在您的 ViewModel 中执行用例
launch {
   state.value = ProductDetailViewState.ProductListFetched(getProductsUseCase.execute())
}

有关更多信息和示例,请参阅https://github.com/snellen/umvvm

【讨论】:

    猜你喜欢
    • 2016-08-16
    • 2018-06-04
    • 2019-12-20
    • 1970-01-01
    • 1970-01-01
    • 2017-10-03
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    相关资源
    最近更新 更多