вAndroid

MODEL-VIEW-INTENT паттерн в Android. Часть 1. Что такое модель?

Существует много архитектурных паттернов в Android (и за пределами): MVC, MVP, MVVM. Все эти паттерны содержат модель. Но что такое модель?

Для примера рассмотрим код (загрузка пользователей с backend)  в интерпретации MVP паттерна:

Что такое модель в этом примере? Модель с backend? Нет это бизнес логика. Может это список пользователей? Опять нет — это всего лишь часть данных которые отображает наша View. Так что же такое модель?

С моей точки зрение модель должна выглядеть так:

А презентер вот так:

Теперь у View есть модель,которую она и отображает. Эта концепция не новая. В оригинальном описании MVC паттерна от Реенскауга Трюгве говорится почти о том же: View  наблюдает за изменением Модели. Но, к сожалению, MVC, описываемый во многих шаблонах, не то же самое что сформулировал Реенскауг Трюгве в 1979 году.

Backend разрабочики используют MVC фреймворки, у  iOS  разработчиков есть  ViewController. Но! Где же  MVC в Android? Является  ли Activity контролером? Что такое ClickListener? В наше время MVC совсем не то, что сформулировал однажды Реенскауг… Но давайте продолжим, пока не утонули в холиварах.

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

  1. Проблема состояния приложения (State problem)
  2. Смена ориентации экрана
  3. Навигация по back stack
  4. Уничтожение процесса/программы (Process death)
  5. Неизменяемость данных (Immutability and unidirectional data flow)
  6. Отладка и воспроизведение состояния
  7. Возможность тестирования

Давайте поговорим отталкиваясь от этих проблем как решаются они в традиционном понимании паттерном MVP и MVVM. И посмотрим как правильно сформулированное понятие Модели позволит нам избежать многих подводных камней.

 

Проблема состояния приложения (State problem)

Определим что такое Состояние (State). В большинстве случаев состояние понимается как то, что в данный момент показано на экране, например “Состояние загрузки контента”, когда View отображает ProgressBar.

Мы ориентируемся на состояние UI, состояние интерфейса. И в этом кроется суть. Давайте посмотрим на код выше (который идет до использования PersonsModel). Вы можете заметить, что состоянием UI управляет Presenter. Типовая работа происходит и в паттерне MVVM.

Но хотелось бы разделить реализации паттерна MVVM на два подхода: первый это Android data binding  и второй по средствам RxJava. В первом варианте состояние хранится в ViewModel:

В реализации же MVVM с RxJava не используется механизм data bindin, но прослушиваем состояние UI через Observable (см. RxJava), например:

Конечно код не идеален и есть, что добавить. Смысл в том что, в MVP и MVVM состояние реализуется через Presenter или ViewModel.

Из этого можно сделать следующие выводы:

  1. Бизнес логика имеет свое состояние, Presenter (или ViewModel) — свое (вы можете синхронизировать эти состояния, они похожи) и View также может иметь состояние.
  2. Presenter (или ViewModel) может иметь множество методов управления (которые вызывает View, например) и это нормально. Но таким же образом  Presenter может иметь множество выходов (например данных, методов для View, таких как view.showLoading() или view.showError() в MVP). Все это множество может привести к конфликтующим состояниям View, Presenter и бизнес логики, особенно когда работа происходит со множеством потоков.

В этом множестве состояний и входов — выходов управления легко “поймать” баг. Например, такой как на видео, где показывается индикатор(состояние загрузки) загрузки и ошибка(состояние ошибки):

Такие ошибки очень сложно отловить и тем более повторить.

Что если сделать один источник состояния? Мы уже говорили об этом в начале статьи, когда обсуждали вопрос “Что такое модель?”.

Догадываетесь? Модель реагирует на состояние. Как только я это понял — многие проблемы связанные с состоянием (state) оказались решены и презентер имеет только один выход getView().render(PersonsModel) . Это похоже на функцию f(x) = y (так же возможно множество аргументов). И в отличии от программного кода, математика не знает багов =)

Важно понимать, что такое «Модель» и как правильно ее опеределить, потому что в итоге Модель может решить «Проблему состояния»

 

 Смена ориентации экрана

Самый простой путь  — это игнорировать смену ориентации. Просто перезагружать все данные после смены ориентации экрана. Это вполне обоснованное решение. В большинстве случаев ваше приложение работает в автономном режиме, поэтому данные поступают из локальной базы данных или из кэша. Поэтому загрузка данных происходит очень быстро после изменения ориентации экрана. Таким образом, разработчики начали использовать MVP с «retaining presenter», чтобы можно было отвязать (и уничтожить)  View во время смены ориентации экрана, тогда как Presenter остается в памяти, а затем вновь привязать View к Presenter. Подобное решение существует и в MVVM с RxJava.

Но есть проблема с сохранением Presenter’а (или ViewModel) : как восстановить состояние View в состояние до поворота экрана, так чтобы View и Presenter были в таком же состоянии? В библиотеке Mosby это решено через ViewState которая синхронизирует состояние бизнес логики с View. Moxy, другая MVP библиотека, реализует интересный подход через “команды” для восстановления состояния View:

Я уверен что так же существуют и другие решения связанные с проблемой состояния. И так, эти библиотеки решают проблему состояния, о которой мы уже говорили.

И снова имеем “Модель” которая отражает текущее “Состоние” и только один метод “render” (getView().render(PersonsModel)). Решение проблемы 2 практически сводится к решению проблемы 1

 

 Back stack навигация

Нужно ли хранить Presenter (ViewModel) когда View больше не используется? Например, если Fragment (View) был заменен на другой Fragment, потому что пользователь перешел на другой экран, то нет более View прикрепленной  к Presenter. Если View не прикреплена к Presenter, то очевидно она не сможет обновиться до свежих данных. А что если пользователь решил вернуться обратно на экран (например нажал кнопку назад)? Перезагружать данные или переиспользовать Presenter? Это уже философский вопрос =). Обычно при возврате на предыдущий экран пользователь ожидает увидеть его таким каким он был(таким каким пользователь его “оставил”). Это в большинстве своем “проблема восстановления состояния View(см пункт 2). Решение такое же: модель восстанавливает состояния путем вызова метода у View getView().render(PersonsModel) .

 

 Управление процессами

Распространенной ошибкой в мире разработки под Android считается то, что уничтожение процессов плохая штука и нам нужны библиотеки которые помогут сохранить и восстановить состояние после уничтожения процесса. Ну во-первых процесс уничтожается тогда, когда ОС  Android  нужно больше ресурсов для других приложений или для сохранения заряда батареи. Но этого никогда не произойдет, если приложение активно (на переднем плане) и пользователь пользуется им в этот момент. Если же вам действительно нужно выполнить длительную задачу — используйте  Service, как вариант, чтобы сообщить  операционной систему, что ваше приложение до сих пор активно используется.

При уничтожении процесса  Android обеспечивает вызов метода onSaveInstanceState() для сохранения состояния. И снова Состояние =) (State).  Следует ли сохранить информацию с  View в   Bundle? Имеет ли  Presenter свое состояние, которое тоже следует сохранить в  bundle? А что на счет состояния бизнес логики? Мы уже описали эти вопросы в пункта 1 — 3. Для этого нам нужен класс Model, который восстанавливает/сохраняет “все” состояния. К слову, достаточно часто не нужно ничего сохранять =)

 

Похожие посты

Добавить комментарий