Interactors/UseCases 的 MVVM 架构
MVVM architecture with Interactors/UseCases
上下文
所以,我一直在为几个项目使用 MVVM 架构。我仍在努力弄清楚并改进架构的工作方式。我一直使用 MVP 架构,使用常用的工具集,用于 DI 的 Dagger,通常是多模块项目,Presenter 层被注入一堆 Interactors/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)
}
}
这里没有火箭科学,有时我会更改实现以使用多个 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)
}
)
}
}
我的存储库 类 通常是 Retrofit 实例的包装器,它们负责设置正确的调度程序,以便一切都在正确的线程上运行并将后端响应映射到模型 类。通过后端响应,我的意思是 类 与 Gson 映射(例如
ApiProductResponse 列表)并将它们映射到模型 类(例如我在应用程序中使用的产品列表)
问题
我的问题是,自从我开始使用 MVVM 体系结构所有文章和所有示例以来,人们要么将存储库直接注入 ViewModel(复制代码以处理错误并映射响应),要么使用Single Source of Truth 模式(使用 Room 的 Flowables 从 Room 获取信息)。但我还没有看到有人将 UseCases 与 ViewModel 层一起使用。我的意思是它非常方便,我可以将事物分开,我在用例中映射后端响应,我在那里处理任何错误。但是,我仍然觉得很奇怪,我没有看到有人这样做,有没有什么方法可以改进 UseCases,使它们在 API 方面对 ViewModels 更友好?使用回调接口以外的其他方式执行用例和 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
我刚刚开始在我的最后两个项目中使用 MVVM。我可以与您分享我在 ViewModel 中处理 REST API 的过程。希望对你和其他人有所帮助。
- 制作一个 Generic Retrofit Executer Class 及其回调。这将采用改造调用对象并为您提供数据。
- 为您的特定包或模块创建一个存储库,您可以在其中处理所有 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
})
}
}
我希望这种方法可以帮助您减少一些样板代码。
谢谢
前段时间开始使用MVVM时,我也有同样的疑问。基于 Kotlin 挂起函数和协程,我提出了以下解决方案:
- 将ApiProductRepositoryImpl.fetchProducts() 同步更改为运行。为此,将改造接口更改为 return Call<...> 然后将存储库实现更改为
// error handling omitted for brevity
override fun fetchProducts() = retrofitApi.fetchProducts().execute().body()
- 让您的用例实现以下接口:
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()
}
- 在您的 ViewModel 中执行用例
launch {
state.value = ProductDetailViewState.ProductListFetched(getProductsUseCase.execute())
}
有关更多信息和示例,请参阅 https://github.com/snellen/umvvm。
更新您的用例,使其 returns 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>
的改编,但这可以通过使用辅助函数映射列表来处理。
上下文
所以,我一直在为几个项目使用 MVVM 架构。我仍在努力弄清楚并改进架构的工作方式。我一直使用 MVP 架构,使用常用的工具集,用于 DI 的 Dagger,通常是多模块项目,Presenter 层被注入一堆 Interactors/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)
}
}
这里没有火箭科学,有时我会更改实现以使用多个 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)
}
)
}
}
我的存储库 类 通常是 Retrofit 实例的包装器,它们负责设置正确的调度程序,以便一切都在正确的线程上运行并将后端响应映射到模型 类。通过后端响应,我的意思是 类 与 Gson 映射(例如 ApiProductResponse 列表)并将它们映射到模型 类(例如我在应用程序中使用的产品列表)
问题
我的问题是,自从我开始使用 MVVM 体系结构所有文章和所有示例以来,人们要么将存储库直接注入 ViewModel(复制代码以处理错误并映射响应),要么使用Single Source of Truth 模式(使用 Room 的 Flowables 从 Room 获取信息)。但我还没有看到有人将 UseCases 与 ViewModel 层一起使用。我的意思是它非常方便,我可以将事物分开,我在用例中映射后端响应,我在那里处理任何错误。但是,我仍然觉得很奇怪,我没有看到有人这样做,有没有什么方法可以改进 UseCases,使它们在 API 方面对 ViewModels 更友好?使用回调接口以外的其他方式执行用例和 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
我刚刚开始在我的最后两个项目中使用 MVVM。我可以与您分享我在 ViewModel 中处理 REST API 的过程。希望对你和其他人有所帮助。
- 制作一个 Generic Retrofit Executer Class 及其回调。这将采用改造调用对象并为您提供数据。
- 为您的特定包或模块创建一个存储库,您可以在其中处理所有 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
})
}
}
我希望这种方法可以帮助您减少一些样板代码。 谢谢
前段时间开始使用MVVM时,我也有同样的疑问。基于 Kotlin 挂起函数和协程,我提出了以下解决方案:
- 将ApiProductRepositoryImpl.fetchProducts() 同步更改为运行。为此,将改造接口更改为 return Call<...> 然后将存储库实现更改为
// error handling omitted for brevity
override fun fetchProducts() = retrofitApi.fetchProducts().execute().body()
- 让您的用例实现以下接口:
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()
}
- 在您的 ViewModel 中执行用例
launch {
state.value = ProductDetailViewState.ProductListFetched(getProductsUseCase.execute())
}
有关更多信息和示例,请参阅 https://github.com/snellen/umvvm。
更新您的用例,使其 returns 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>
的改编,但这可以通过使用辅助函数映射列表来处理。