Как стать автором
Обновить

Комментарии 14

Радоваться или грустить… Новичкам точно усложняют задачу. А вот люди, которые как-то пытались реализовать MVP, должны быть в восторге.
Имхо, в андроиде уже была куча разных подходов к архитектуре MVP, MVI, MVVM, Clean Arhitecture, но многие новички как то кодят и без низ, так что на мой взгляд еще один подход сильно жизнь новичкам не усложнит, и даже скорее наоборот хорошо, что появилось приятное решение из коробки.
Печаль тоска. Компат станет ещё жирнее
Но в то же время они его облегчают путем отбрасывания поддержки старых SDK.
Да и в общем-то кода там немного, а Room идет отдельно.
Собственно, Lifecycle тоже сейчас отдельно, но это, как я понимаю, временно.
НЛО прилетело и опубликовало эту надпись здесь
Ну не настолько же старых
НЛО прилетело и опубликовало эту надпись здесь

С LiveData и Activity понятно, но со ViewModel — не очень.


Предполагается, что все данные — во ViewModel. И если какие-то из них — LiveData, то ViewModel подписана на них, а Activity — во ViewModel? Как обрабатывается стейт активити внутри ViewModel?


Однако, сами Entity не могут содержать поля-ссылки на другие таблицы, это связано с тем, что ленивая (lazy) подгрузка данных при обращении к ним начнется в том же потоке и наверняка это окажется UI-поток. Поэтому Google приняла решение запретить полностью такую практику.

Но ведь помимо Lazy есть другие способы загрузить свойство навигации (в терминах EF).

Нет, ViewModel не подписывается на LiveData. Хранит только ссылки на такие объекты, не более того.
Предполагается, что все данные — во ViewModel. И если какие-то из них — LiveData, то ViewModel подписана на них, а Activity — во ViewModel? Как обрабатывается стейт активити внутри ViewModel?

По сути никак. Событие onCleared — единственная точка влияния стейта активити на ViewModel. Интеллектуальная обработка стейта происходит целиком, полностью и независимо в каждой LiveData, которые модель выставляет наружу. Если вам в модели надо знать состояние активити, надо вручную сделать метод addLifecycle(Lifecycle lifecycle), который каждая активити вызовет как addLifecycle(this.getLifecycle()). А дальше в модели подписываться на события каждого Lifecycle и вручную отрабатывать, что, например, модель должна делать, если два активити стали PAUSED и один ACTIVE.
Я полагаю, что как правило LiveData в модели будут данные брать не из воздуха, а получая их из LiveData в Repository. Это делается через трансформацию (аналог операторов в rxJava):


// В WeatherRepository:

public LiveData<WeatherInfo> getWeather(String cityName) {
  // Данные из БД
}

// В MainActivityViewModel:

private final MutableLiveData<String> cityNameLiveData = new MutableLiveData<>();
private final LiveData<WeatherInfo> weatherInfoLiveData;

public MainActivityViewModel() {
     this.weatherInfoLiveData = Transformations.switchMap(cityNameLiveData, weatherRepository::getWeather);
}

// Вызывает UI, когда хочет получить данные по наименованию города
public void selectCity(String cityName) {
   cityNameLiveData.setValue(cityName);
}

// На это UI подписывается
public LiveData<WeatherInfo> weatherInfo() {
     return weatherInfoLiveData;
}

Здесь у нас есть один инстанс weatherInfoLiveData, в котором живут стейты подписавшихся активити и Transformations.switchMap, который говорит "каждый раз, когда cityNameLiveData пришлет новые данные, вызови weatherRepository.getWeather(cityNameLiveData.getValue) и подпишись на тот LiveData, который он вернет, а все эмитируемые им данные передавай в weatherInfoLiveData".
Если при этом данные надо преобразовать, то можно еще использовать Transformation.map(LiveData source, Function func).

Не совсем корректно написал: конечно же активити у модели может быть только одна, следовательно, и Lifecycle единственный, а вот если мы модель привязываем к нескольким фрагментам, то каждый из них вызовет addLifecycle(this.getLifecycle()) и мы получим в модели список из нескольких Lifecycle.

Но ведь помимо Lazy есть другие способы загрузить свойство навигации (в терминах EF).

Google говорит так:


However, on the client side, lazy loading is not feasible because it's likely to happen on the UI thread, and querying information on disk in the UI thread creates significant performance problems.

А, если загружать сразу?


If you don't use lazy loading, however, the app fetches more data than it needs, creating memory consumption problems.

Поэтому


For these reasons, Room disallows object references between entity classes. Instead, you must explicitly request the data that your app needs.

Addendum: No object references between entities

Как с LiveData обрабатывать и показывать ошибки? Заводить отдельный объект LiveData для хранения последних ошибок? И так для каждого действия на экране?

Тот же самый вопрос и про успешное выполнение какого-либо действия, например, сохранение данных.

Проблему с поворотом Activity более менее я понял как LiveData решает. А что с убийством Activity при сворачивании приложения?

LiveData не имеет встроенных механизмов обработки ошибок. Придется делать что-то типа LiveData<Data>, где


class Data {
  Status status;
  Object payLoad; // Полезная нагрузка, ради которой все затевается
  String errorMessage;
}

enum Status { SUCCESS, LOADING, FAIL }

и передавать руками статусы и сообщения об ошибках вместе с данными. Соответственно, создалась Activity, подписалась на LiveData в своей ViewModel и сразу получила закешированные (или пустые данные) + статус. Если статус LOADING, включается progress, иначе выключается.
Такой подход рекомендует Google на примере обработки состояния сетевого запроса: Addendum: exposing network status


Любопытно, кстати, что если мы вызовем несколько раз liveData.setValue с одинаковым значением, обзерверы так же будут вызваны несколько раз с одним и тем же значением.


Проблему с поворотом Activity более менее я понял как LiveData решает. А что с убийством Activity при сворачивании приложения?

Внутри фреймворка хранение моделей реализовано через специальные retained fragments. Соответственно, если Activity будет убита системой, ViewModel и все LiveData будут также убиты. И весь процесс начнется сначала, как будто приложение запускают впервые.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории