Comments 24
Просто предложенный топикстартером вариант нарушает многие заветы Clean Architecture — поэтому неправильно называть все это "чистой арзитектурой". Но что тогда остается — просто yet another велосипед, коих и так уже не сосчитать. Чтобы разобраться в чистой архитектуре — рекомендую видео одного из ее отцов :) Это будет хорошим введением, но придется еще почитать статей для углубления.
1. В модели из сети данные приходят в одном формате, а на стороне UI зачастую приходится работать с данными в другом формате (даты, суммы, id из справочников и т.д.)
2. Если в этот пример нужно будет добавить работу с БД — UI слой придется переписать. Кстати, сюда же можно и отнести первый пункт — в БД данные удобно хранить в других структурах и форматах и частенько они не совпадают с тем, что приходит из сети.
3. Ну и конечно, никто не застрахован от того что сетевая модель останется неизменной (:
p.s. кучу абстракций городить не нужно, например — поля модели можно вынести в интерфейс.
— в методе requestWithCallback() в блоке try результат доставляется в main-потоке, а в блоке catch ошибка доставляется уже в io-потоке — потенциальный крэш во viewOneError()
— data-класс Event вместе с enum Status лучше и компактнее будет выглядеть в виде sealed-класса
— отдавать MutableLiveData наружу из ViewModel — плохая практика. наружу должна торчать LiveData (через backing-property или какой-нибудь get-метод)
Позвольте немного критики решения.
Api-класс сделан синглтоном — тестировать (мокать) будет сложнее. Лучше бы это был обычный класс, зависимость на который вы передавали бы через DI-фреймворк.
Cоздание внешних зависимости внутри класса — уже плохо для тестирования и поддержки, а вы еще и синглтон используете в этой зависимости:
abstract class BaseViewModel : ViewModel() {
var api: Api = NetworkService.retrofitService()
К тому же, такая жесткая зависимость создается в базовом классе, который в реальной жизни будут использовать все другие модели. Лучше бы взять принцип IoC, который активно используется в Clean Architecture — это избавит от жестких связей и позволит менять зависимость как угодно в рамках интерфейса. Опять же, полезно не только для тестов, но и для реальной жизни и непрогнозируемого развития приложения.
Все эти громоздкие конструкции с try-catch, на мой взгляд, только ухудшают читаемость кода… Почему бы не использовать Rx, с его идеальной логикой обработки ошибок и все теми же состояниями Complete, Error уже из коробки? Если уж использовать try-catch по старинке, то лучше вынести эти блоки в отдельные приватные методы — серьезно улучшите читаемость.
Если вам зашла лайв дата, можно использовать ее адаптер для ретрофита и получать сетевые модели без этих странных конвертаций на уровне ViewMode, которая вообще не должна этим заниматься (не ее зона ответственности):
liveData.postValue(Event.success(response.data))
Сложилось впечатление, что под «чистой» архитектурой вы понимаете что-то иное, не подход Clean Architecture. И это даже не цепляясь к преобразованиям модели между уровнями (которые не всегда нужны) и обращению к сети прямо из ViewModel (что тоже возможно в совсем маленьких приложениях).
Материал будет полезен тем, кто не очень хорошо знаком с mvvm-паттерном и котлиновскими корутинами.
Фишка в том, что, когда я изучал все эти используемые в статье компоненты, я постоянно натыкался на плохо раскрываемый для понимания материал. Поэтому постарался написать для людей «кто не очень хорошо знаком с mvvm-паттерном и котлиновскими корутинами», более развернуто и наглядно, как это все работает в связке. Статья и так получилась слишком длинная, для чего все это здесь? Я имею ввиду принцип IoC, преобразования моделей в разных слоях и пр. замечания из комментариев выше?
Ваше негодование тоже понятно, но непонятную хрень вы ведь тоже добавили? Абстрактная модель, лайв дата, обертки и все вот это. Если уж хочется рассказать просто про MVVM — половину можно выкинуть без потери смысла.
sealed class Event<T : Any> {
var isHandled: Booleand = false
data class Success<T>(val data: T) : Event<T>()
data class Error(val message: String, val error: Throwable?) : Event<Nothing>()
object Loading : Event<Nothing>()
}
это дает некую мутабельность ивентов при пересечении архитектурных слоев если использовать Clean Architecture. Например, DataLayer может работать только с
Event<DatabaseEntity>
, а PresentationLayer может принимать только Event<DataUIModel>
, тогда мы можем более лучше разграничить какие объекты могут пересекать архитектурные слои.Конвертер в данном случае будет простой функцией
inline fun <reified T : Any, reified R : Any> Event<T>.mutateTo(): Event<R> {
// ваша логика конвертации
}
“If you think that good architecture is expensive, try bad architecture.”
— Brian Foote and Joseph Yoder
Организация простой архитектуры в андроид-приложении со связкой ViewModel+LiveData, Retrofit+Coroutines