Архитектура MVVM с использованием Interactors / UseCases - PullRequest
0 голосов
/ 30 марта 2019

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

1 Ответ

0 голосов
/ 30 марта 2019

Я только начал использовать MVVM для двух последних моих проектов.Я могу поделиться с вами своим процессом работы с REST API в ViewModel .Надеюсь, это поможет вам и другим.

  • Создайте Универсальный Модернизированный Исполнитель Модификации с их обратными вызовами.который примет объект вызова дооснащения и предоставит вам данные.
  • Создайте репозиторий для вашего конкретного пакета или модуля, в котором вы сможете обрабатывать все запросы API.в моем случае я получаю одного пользователя по его идентификатору из API. Вот пользовательский репозиторий.

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
         })
     }
}

Я надеюсь, что этот подход поможет вам сократить некоторые из ваших шаблонов кода.Спасибо

...