Как написать удобную для чтения и отладки ViewModel

Примечания

Хотя в примере используется модель представления Android Jetpack, эти концепции можно применить к общей модели представления с помощью других методов, таких как привязка данных.

Связь между View и ViewModel по существу использует шаблон проектирования Observer. В частности, View поручает ViewModel обрабатывать всю необходимую логику. Всякий раз, когда работа сделана и результат доступен, ViewModel публикует / передает результат в некоторые потоки. View получает уведомление каждый раз, когда появляется новое значение в каком-либо потоке и обновляется пользовательский интерфейс рендеринга (текст TextView, цвета фона и т. Д.) Или выполняется соответствующее действие (переход на другой экран, отображение диалогового окна).

Приложение

UI

Допустим, у нас есть экран профиля в приложении, похожем на Instagram, которое состоит из

  • Аватар пользователя
  • Имя пользователя
  • Фотографии (только если профиль общедоступный или вы уже являетесь подписчиком)

Логика

Для простоты приложение получает данные о пользователе с удаленного сервера и отображает их в случае успеха. В случае ошибки, такой как потеря соединения, мы отображаем значок и сообщение об ошибке в зависимости от сценария.

Используя архитектуру MVVM с Jetpack ViewModel, естественно придумать что-то вроде этого:

Здесь есть много моментов, когда ViewModel сообщает View повторно визуализировать материалы mutableLiveData.value = ….

Это должно хорошо работать для небольшой модели ViewModel с несколькими свойствами ViewModel (каждое из которых влияет на набор элементов представления в представлении). Однако, когда их десятки (что часто бывает в больших коммерческих приложениях), легко ошибиться, трудно читать и трудно устранять неполадки.

Глядя на представление, можно увидеть множество возможностей для состояний гонки (например, состояние «загружается») и ошибок, которые трудно обнаружить:

Чтобы было лучше, давайте разделяем и властвуем (длинный главный список свойств ViewModel!

ViewState

Мы можем упростить чтение, отладку и тестирование, сгруппировав эти значения по «состоянию» представления (Activity, Fragment, Custom View).

В нашем случае всего 3 состояния

  • Загрузка
  • Данные пользователя загружены успешно
  • Не удалось загрузить данные пользователя / состояние ошибки

Точно известно, что общее количество «состояний просмотра» невелико и требует гораздо большего управления, чем количество свойств ViewModel.

Это разбивает процесс чтения и устранения неполадок большой ViewModel на 2 более простых шага:

  • Фактическое или ожидаемое «состояние просмотра»?
  • Для этого конкретного «состояния просмотра» фактические и ожидаемые свойства?

Наша ViewModel становится

Наш взгляд становится

ActionState

Такие свойства, как String, Boolean и т. Д., Не единственные ожидаемые «результаты» ViewModel. Помимо свойств визуализации, View может также выполнять такие действия, как переход к другому экрану или отображение диалоговых окон.

Например, когда пользователь выбирает «выйти из всех устройств» на настольном компьютере, серверная часть сообщает нашему сетевому уровню ››… ›› нашей модели ViewModel создать результат «Требуется выход», и View должен это обработать. . Довольно часто можно завершить текущий экран, очистить все необходимые локальные данные и перейти к экрану входа / регистрации.

Можно было бы сделать его другим типом ViewState, и это сработало бы.

Однако есть 3 причины, по которым нам нужен отдельный тип для этой цели:

  • Указанные выше свойства следует сохранять и восстанавливать при повороте экрана. Нет смысла пытаться сохранить состояние «выйти из системы и перейти к экрану входа в систему» ​​после поворота экрана. Такое состояние не подлежит восстановлению. Другой пример: мы иногда показываем пользователю диалоговое окно, в котором он принимает последние Условия использования. Их будет чертовски раздражать, если мы покажем диалоговое окно снова, когда пользователь поворачивает экран после того, как он уже принял Условия!

«Перейти к другому состоянию экрана» не предназначен для восстановления

Во многих случаях нет смысла воспроизводить диалог после поворота экрана.

  • Что-то вроде связанного с 1-м пунктом, «перейти к проверке» происходит только один раз во время сеанса просмотра сведений о пользователе. Принимая во внимание, что значения свойств, таких как имя пользователя, могут быть установлены несколько раз в течение сеанса.
  • Снова разделяй и властвуй

Наша ViewModel становится

Наш взгляд становится

Наша диаграмма немного изменилась, так как теперь есть 2 наблюдателя вместо одного.

Обратите внимание, как SingleLiveEvent<ActionState> используется вместо LiveData<ActionState> здесь. Он решает 2 проблемы, упомянутые для ActionState:

  • ActionState, например «показать диалог: принять обновления T&C», не следует воспроизводить повторно после поворота экрана.

  • Сохранять состояние «перейти на другой экран» бессмысленно.

Исходный код

Окончательная версия: https://github.com/ericntd/supercharged-mvvm

Ограничения и улучшения

  1. Вместо того, чтобы позволить View получить доступ к нашим MutableLiveData, я лично делаю

Это выглядит немного избыточным и некрасивым и кажется немного хрупким. У вас есть предложения получше?

2. Мне никогда не требовалось более одного наблюдателя (самого View) для ActionState, поэтому SingleLiveEvent<ActionState> suffices. Однако, если вам нужно иметь более 1 наблюдателя, рассмотрите возможность использования оболочки событий.



Следующий

Как ViewModel может получать такие события, как нажатия кнопок?