На недавнем DroidCon Moscow 2016 был доклад о MVVM c Databinding Library и доклад о библиотеке Moxy, помогающей работать с MVP. Дело в том, что за последние полгода мы успели опробовать оба подхода на живых проектах. И я хочу рассказать о своём пути от освоения Databinding Library и выпуска в продакшн проекта на MVVM до осознания, почему я больше не хочу использовать этот паттерн. Начав разбираться с Databinding Library, я был под впечатлением. Те, кто уже знаком с ней, меня поймут, а для остальных, вот как выглядит работа с этой библиотекой:  Использование Databinding Library позволяет: Последний пункт особенно приятен для меня потому, что состояния всегда были сложной темой. Если на экране нужно отобразить три состояния (загрузка, данные, ошибка), это ещё ладно. Но, когда появляются различные требования к состоянию элементов в зависимости от данных (например, отображать текст только если он не пустой, или менять цвет в зависимости от значения), может понадобиться либо большой switch cо всеми возможными вариантами состояний интерфейса, либо много флагов и кода в методах установки значений элементам.
Поэтому то, что Databinding Library позволяет упростить работу с состояниями, – огромный плюс. К примеру, написав в xml android:visibility=”@{user.name != null ? View.VISIBLE : View.GONE}”, мы можем больше не думать о том, когда надо скрыть или показать TextView с именем пользователя. Мы просто задаём имя, а видимость изменится автоматически. Но, начав использовать databinding активнее, вы получите в xml всё больше и больше кода. И, чтобы не превращать layout в свалку, мы создадим класс, в который вынесем этот код. А в xml будут оставаться только вызовы свойств. Приведу маленький пример. Предположим, есть класс User: А в UI мы хотим видеть полное имя и пишем в xml: Это не очень хочется видеть в xml, и мы создаём класс, в который выносим эту логику: Создатели библиотеки предлагают называть такие классы ViewModel (прям, как в паттерне MVVM, удивительно).  Теперь в xml у нас будет:  Гораздо лучше, не правда ли?  Итак, у нас появился ViewModel класс, который выступает в роли прослойки между данными и view. Он занимается преобразованиями данных, управляет тем, какие поля (и связанные элементы UI) и когда обновляются, содержит логику того, как одни поля зависят от других. Это позволяет очистить xml от кода. Кроме того, удобно использовать этот класс для обработки событий из view (нажатия и т.п). И тут к нам приходит мысль: Если у нас уже есть databinding, есть ViewModel класс, содержащий логику отображения, то почему бы не использовать паттерн MVVM? Эта мысль приходит неизбежно. Потому что то, что мы имеем в данный момент очень и очень близко к тому, что из себя представляет паттерн MVVM. Давайте кратко его рассмотрим. В паттерне Model-View-ViewModel три основных компонента:  Связь и взаимодействие между собой этих компонентов мы видим на картинке:  Стрелками показаны зависимости: View знает о ViewModel, а ViewModel знает о Model, но модель ничего не знает о ViewModel, которая ничего не знает о View.  Процесс такой: ViewModel запрашивает данные у Model и обновляет её когда необходимо. Model уведомляет ViewModel, что данные есть. ViewModel берёт данные, преобразует их и уведомляет View, что данные для UI готовы. Связь между ViewModel и View осуществляется путём автоматического связывания данных и отображения. В нашем случае это достигается через использование Databinding Library. При помощи databinding’а View обновляется, используя данные из ViewModel. Так я начал использовать MVVM в своем проекте. Но, как часто бывает в программировании, теория и практика – не одно и тоже. И после завершения проекта у меня осталось чувство неудовлетворенности. Что-то было не так в этом подходе, что-то не нравилось, но я не мог понять, что именно.  Тогда я решил нарисовать схему MVVM на Android:  Рассмотрим, что в итоге получается: ViewModel содержит поля, используемые в xml для биндинга данных (android:text=”@{viewmodel.username}”), обрабатывает события вызванные на View (android:onClick=”@{viewmodel::buttonClicked}”). Она запрашивает данные у Model, преобразует их, и при помощи databinding’a эти данные попадают во View. Fragment одновременно выполняет две роли: входная точка, обеспечивающая инициализацию и связь с системой, и View.  То, что Fragment (или Activity) рассматриваются как View в понимании паттернов MVP и MVVM, уже стало распространённой практикой, поэтому я не стану на этом останавливаться. Чтобы пережить повороты и пересоздание Activity, мы оставляем ViewModel жить на то время, пока пересоздаётся View (в нашем случае Fragment). Достигается это с использованием dagger и пользовательских scopes. Не стану вдаваться в подробности, уже написано много хороших статей про dagger. Своими словами, происходит следующее:  При необходимости сохранения состояния в случае, когда система убила приложение, мы можем сохранять и восстанавливать состояние ViewModel, используя savedInstanceState фрагмента. Примерно так всё работает.  Внимательный читатель спросит: «A чего мучаться с dagger custom scopes, если можно просто использовать Fragment как контейнер и вызвать в нём setRetainInstance(true)?» Да, так сделать можно. Но, рисуя схему, я учитывал, что в качестве View можно использовать Activity или ViewGroup. Нарисовав схему и обдумав всё, я понял, что именно меня не устраивало во время работы с этим подходом. Взгляните на схему снова. Видите толстые стрелки «databinding» и «manual commands to view»? Вот оно. Сейчас расскажу подробнее. Раз у нас есть databinding, то большую часть данных мы можем просто устанавливать в View при помощи xml (создав нужный BindingAdapter, если понадобится). Но есть случаи, которые не укладываются в этот подход. К таким относятся диалоги, toast’ы, анимации, действия с задержкой и другие сложные действия с элементами View.  Вспомним пример с TextView: Что, если нам нужно установить этот текст, используя view.post(new Runnable())? (Не думаем зачем, думаем как) Можно сделать BindingAdapter, в котором создать атрибут «byPost», и сделать, чтобы учитывалось наличие перечисленных атрибутов у элемента. И теперь каждый раз, когда у TextView будут указаны оба атрибута, будет использоваться этот BindingAdapter. Добавим атрибут в xml: ViewModel теперь должно иметь свойство, указывающее на то, что в момент установки значения мы должны использовать view.post(). Добавим его: Видите, сколько всего нужно сделать, чтобы реализовать очень даже простое действие?  Поэтому гораздо проще делать подобные вещи прямо на View. То есть использовать интерфейс MvvmView, который реализуется нашим фрагментом, и вызывать методы View (так же, как это обычно делается в MVP). Теперь расскажу о ещё одной проблеме. Представим ситуацию с поворотом телефона.  Получается, что нужно как-то хранить не только состояние View, представленное набором полей ViewModel, но также и методы, которые ViewModel вызывает на View. Эту проблему можно решить, заводя во ViewModel поля-флаги на каждый отдельный такой случай. Это не очень-то красиво и не универсально. Но работать будет. Проблема состояний натолкнула меня на мысли, что состояние объекта можно воссоздать двумя путями: набором параметров, характеризующим состояние, или набором действий, которые необходимо совершить, чтобы привести объект в нужное состояние. Представьте себе кубик Рубика. Его состояние можно описать 9 цветами на одной из граней. А можно набором движений, которые приведут его из начального состояния в требуемое.   Может понадобиться всего один поворот, а может и намного больше девяти. Получается, в зависимости от ситуации, какой-то способ описания состояния лучше или хуже (меньше данных нужно). Обдумывая способы воссоздания состояния, я не мог не вспомнить о библиотеке Moxy. Мои коллеги параллельно делали проект, используя паттерн MVP и эту библиотеку. Подробно я о ней рассказывать не стану, уже есть отличная статья от авторов.  В контексте моих рассуждений интересна одна особенность Moxy – она хранит состояние view как набор команд, вызванных на этой view. И, когда я узнал об этом впервые, мне это показалось странным.  Но теперь, после всех размышлений (которыми я поделился с вами выше), я думаю, что это очень удачное решение.
Потому что: Кроме того, этот подход даёт ещё один плюс. Он также, как и Databinding Library, по-своему решает проблему большого количества разных состояний. Тоже не придется писать огромный switch, изменяющий UI в зависимости от набора полей или названия одного из состояний, так как изменения воссоздаются набором вызовов методов. И всё же я не могу совсем ничего больше не сказать про Moxy. По моему мнению и мнению моих коллег, на сегодняшний день она является лучшей библиотекой, которая помогает наладить работу с паттерном MVP. Она использует генерацию кода, чтобы минимизировать трудозатраты разработчика. Вы можете не думать о реализации паттерна, а думать о функционале своего проекта. А это хорошо. Но хватит про MVP. Всё-таки речь у нас про MVVM, и пора подвести итоги. Мне нравится MVVM как паттерн, и я не оспариваю его плюсы. Но в большинстве своём они те же самые, что у других паттернов, либо являются делом вкуса разработчика. Да и основной плюс даёт всё же databinding, а не сам паттерн. Ведомый симпатией к MVVM, я реализовал проект на нём. Долго изучал тему, обдумывал, обсуждал и вынес для себя набор минусов этого паттерна: Да, с этими минусами можно свыкнуться. Но после долгих раздумий я пришел к выводу, что не хочу работать с паттерном, который создает раздробленность подходов. И решил, что следующий проект буду писать, используя MVP и Moxy.  Использовать ли вам этот паттерн – решайте сами. Но я вас предупредил. Закончим, пожалуй, тем же, с чего и начали – Databinding Library. Мне она по-прежнему нравится. Но использовать её я собираюсь только в ограниченном количестве: И всё. Это даст плюсы, но не станет манить в сторону MVVM. Если вы тоже планируете работать с Databinding Library, то вот вам немного полезной информации: Android Developer