android 具有交互器/用例的MVVM体系结构

wpcxdonn  于 2023-01-19  发布在  Android
关注(0)|答案(3)|浏览(158)
    • 背景**

因此,我一直在为几个项目使用MVVM架构。我仍然在尝试找出和改进架构的工作方式。我一直在使用MVP架构,使用常用的工具集Dagger for DI,通常是多模块项目,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获得结果时,我通过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示例的 Package 器,它们负责设置适当的Scheduler,以便所有内容都在适当的线程上运行,并将后端响应Map到模型类。后端响应是指与GsonMap的类(例如ApiProductResponse列表),它们被Map到模型类(例如我在整个应用程序中使用的产品列表)

    • 问题**

我在这里的问题是,自从我开始使用MVVM体系结构以来,所有的文章和所有的示例,人们要么将存储库直接注入到视图模型中(复制代码以处理错误并Map响应)或使用单一真实数据源模式(使用Room的Flowables从Room中获取信息)但是我还没有看到任何人使用带有ViewModel层的用例,我的意思是它非常方便,我把事情分开,我在用例中Map后端响应,我处理那里的任何错误。但是,我仍然觉得我没有看到任何人在做这件事,* * 是否有一些方法可以改进用例,使它们在API方面对视图模型更友好?使用回调接口以外的其他接口执行用例和视图模型之间的通信?**
请让我知道,如果你需要任何更多的信息,这一点。对不起的例子,我知道这些不是最好的,我只是出来了一些简单的为了更好地解释它。
谢谢你,

    • 编辑#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
rryofs0p

rryofs0p1#

更新用例,使其返回Single<List<Product>>

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<ApiProductResponse>>List<Product>的适配,但是这可以通过用helper函数Map列表来处理。

at0kjp5o

at0kjp5o2#

我最近的两个项目刚刚开始使用MVVM。我可以和你分享我在ViewModel中处理REST API的过程。希望它能帮助你和其他人。

  • 创建一个通用Retrofit Executer类及其回调函数,该类将接受一个Retrofit调用对象并向您提供数据。
  • 为您的特定包或模块创建一个存储库,您可以在其中处理所有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
         })
     }
}

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

cfh9epnr

cfh9epnr3#

当我不久前开始使用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

相关问题