Урок 4. ViewModel
В этом уроке рассмотрим, как использовать ViewModel. Как сохранять данные при повороте экрана. Как передать Context в ViewModel. Как передать свои данные в конструктор модели с помощью фабрики. Как передать данные между фрагментами. Что использовать: ViewModel или onSavedInstanceState.
Полный список уроков курса:
ViewModel — класс, позволяющий Activity и фрагментам сохранять необходимые им объекты живыми при повороте экрана.
Давайте сразу посмотрим пример и по ходу дела разберем нюансы и прочую теорию. Как подключить библиотеку к проекту, вы можете посмотреть в начале Урока 2.
Создаем свой класс, наследующий ViewModel
Пока оставим его пустым.
Чтобы добраться до него в Activity, нужен следующий код:
В метод ViewModelProviders.of передаем Activity. Тем самым мы получим доступ к провайдеру, который хранит все ViewModel для этого Activity.
Методом )» target=»_blank» rel=»noopener noreferrer»>get запрашиваем у этого провайдера конкретную модель по имени класса — MyViewModel. Если провайдер еще не создавал такой объект ранее, то он его создает и возвращает нам. И пока Activity окончательно не будет закрыто, при всех последующих вызовах метода get мы будем получать этот же самый объект MyViewModel.
Соответственно, при поворотах экрана, Activity будет пересоздаваться, а объект MyViewModel будет спокойно себе жить в провайдере. И Activity после пересоздания сможет получить этот объект обратно и продолжить работу, как будто ничего не произошло.
Отсюда следует важный вывод. Не храните в ViewModel ссылки на Activity, фрагменты, View и пр. Это может привести к утечкам памяти.
На картинке время жизни (оно же scope) модели это выглядит так:
Модель жива, пока Activity не закроется окончательно.
У метода get, который возвращает нам модель из провайдера, есть еще такой вариант вызова:
Т.е. вы можете создавать несколько моделей одного и того же класса, но использовать разные текстовые ключи для их хранения в провайдере.
LiveData
LiveData очень удобно использовать с ViewModel. В прошлых уроках я для работы с LiveData использовал синглтон, но теперь мы перейдем на ViewModel.
Рассмотрим несложный пример асинхронной однократной загрузки каких-либо данных:
Основной метод здесь — это getData. Когда Activity захочет получить данные, оно вызовет именно этот метод. Мы проверяем, создан ли уже MutableLiveData. Если нет, значит этот метод вызывается первый раз. В этом случае создаем MutableLiveData и стартуем асинхронный процесс получения данных методом loadData. Далее возвращаем LiveData.
В методе loadData происходит асинхронное получение данных из какого-нибудь репозитория. Как только данные будут получены (в методе onLoad), мы передаем их в MutableLiveData.
Метод loadData должен быть асинхронным, потому что он вызывается из метода getData, а getData в свою очередь вызывается из Activity и все это происходит в UI потоке. Если loadData начнет грузить данные синхронно, то он заблокирует UI поток.
Код в Activity выглядит так:
Получаем от провайдера модель. От модели получаем LiveData, на который подписываемся и ждем данные.
В этом примере ViewModel нужен, чтобы сохранить процесс получения данных при повороте экрана. А LiveData — для удобного асинхронного получения данных.
Т.е. это будет выглядеть так:
— Activity вызывает метод модели getData
— модель создает MutableLiveData и стартует асинхронный процесс получения данных от репозитория
— Activity подписывается на полученный от модели LiveData и ждет данные
— происходит поворот экрана
— на модели этот поворот никак не сказывается, она спокойно сидит в провайдере и ждет ответ от репозитория
— Activity пересоздается, получает ту же самую модель от провайдера, получает тот же самый LiveData от модели и подписывается на него и ждет данные
— репозиторий возвращает данные, модель передает их в MutableLiveData
— Activity получает данные данные от LiveData
Если репозиторий вдруг пришлет ответ в тот момент, когда Activity будет пересоздаваться, то Activity получит этот ответ, как только подпишется на LiveData.
Если ваш репозиторий сам умеет возвращать LiveData, то все значительно упрощается. Вы просто отдаете этот LiveData в Activity и оно подписывается.
Очистка ресурсов
Когда Activity окончательно закрывается, провайдер удаляет ViewModel, предварительно вызвав его метод onCleared
В этом методе вы сможете выполнить все необходимые операции по освобождению ресурсов, закрытию соединений/потоков и т.п.
Context
Не стоит передавать Activity в модель в качестве Context. Это может привести к утечкам памяти.
Если вам в модели понадобился объект Context, то вы можете наследовать не ViewModel, а AndroidViewModel.
При создании этой модели, провайдер передаст ей в конструктор класс Application, который является Context. Вы сможете до него добраться методом getApplication.
Код получения этой модели в Activity останется тем же самым.
Передача объектов в конструктор модели
Бывает необходимость передать модели какие-либо данные при создании. Модель создается провайдером и у нас есть возможность вмешаться в этот процесс. Для этого используется фабрика. Мы учим эту фабрику создавать модель так, как нам нужно. И провайдер воспользуется этой фабрикой, когда ему понадобится создать объект.
Рассмотрим пример. У нас есть такая модель
Ей нужен long при создании.
В конструктор передаем long, который нам необходимо будет передать в модель.
В методе )» target=»_blank» rel=»noopener noreferrer»>create фабрика получит от провайдера на вход класс модели, которую необходимо создать. Проверяем, что это класс MyViewModel, сами создаем модель и передаем туда long.
В Activity код получения модели будет выглядеть так:
Мы создаем новую фабрику с нужными нам данными и передаем ее в метод of. При вызове метода get провайдер использует фабрику для создания модели, т.е. выполнится наш код создания модели и передачи в нее данных.
Передача данных между фрагментами
ViewModel может быть использована для передачи данных между фрагментами, которые находятся в одном Activity. В документации есть отличный пример кода:
SharedViewModel — модель с двумя методами: один позволяет поместить данные в LiveData, другой — позволяет получить этот LiveData. Соответственно, если два фрагмента будут иметь доступ к этой модели, то один сможет помещать данные в его LiveData, а другой — подпишется и будет получать эти данные. Таким образом два фрагмента будут обмениваться данными ничего не зная друг о друге.
Чтобы два фрагмента могли работать с одной и той же моделью, они могут использовать общее Activity. Код получения модели в фрагментах выглядит так:
Для обоих фрагментов getActivity вернет одно и то же Activity. Метод ViewModelProviders.of вернет провайдера этого Activity. Далее методом get получаем модель.
Фрагмент MasterFragment помещает данные в LiveData. А DetailFragment — подписывается и получает данные.
onSavedInstanceState
Чем ViewModel отличается от onSavedInstanceState. Для каких данных какой из них лучше использовать. Кажется, что если есть ViewModel, который жив все время, пока не закрыто Activity, то можно забыть про onSavedInstanceState. Но это не так.
Давайте в качестве примера рассмотрим Activity, которое отображает список каких-то данных и может выполнять поиск по ним. Пользователь открывает Activity и выполняет поиск. Activity отображает результаты этого поиска. Пользователь сворачивает приложение. Когда он его снова откроет, он ожидает, что там все останется в этом же состоянии.
Но тут внезапно системе не хватает памяти и она убивает это свернутое приложение. Когда пользователь снова запустит его, Activity ничего не будет знать о поиске, и просто покажет все данные. В этом случае ViewModel нам никак не поможет, потому что модель будет убита вместе с приложением. А вот onSavedInstanceState будет выполнен. В нем мы сможем сохранить поисковый запрос, и при последующем запуске получить его из объекта savedInstanceState и выполнить поиск. В результате пользователь увидит тот же экран, который был, когда приложение было свернуто.
ViewModel — здесь удобно держать все данные, которые нужны вам для формирования экрана. Они будут жить при поворотах экрана, но умрут, когда приложение будет убито системой.
onSavedInstanceState — здесь нужно хранить тот минимум данных, который понадобится вам для восстановления состояния экрана и данных в ViewModel после экстренного закрытия Activity системой. Это может быть поисковый запрос, ID и т.п.
Соответственно, когда вы достаете данные из savedInstanceState и предлагаете их модели, это может быть в двух случаях:
1) Был обычный поворот экрана. В этом случае ваша модель должна понять, что ей эти данные не нужны, потому что при повороте экрана модель ничего не потеряла. И уж точно модель не должна заново делать запросы в БД, на сервер и т.п.
2) Приложение было убито, и теперь запущено заново. В этом случае модель берет данные из savedInstanceState и использует их, чтобы восстановить свои данные. Например, берет ID и идет в БД за полными данными.
RxJava
В начале этого урока мы рассмотрели пример работы ViewModel и LiveData. Возникает вопрос, можно ли заменить LiveData на Flowable?
У LiveData есть одно большое преимущество — он учитывает состояние Activity. Т.е. он не будет слать данные, если Activity свернуто. И он отпишет от себя Activity, которое закрывается.
А вот Flowable этого не умеет. Если в модели есть Flowable, и Activity подпишется на него, то этот Flowable будет держать Activity, пока оно само явно не отпишется (или пока Flowable не завершится).
Давайте рассмотрим пример. ViewModel обычно работает с репозиториями, которые могут быть синглтонами. В репозитории есть какой-то объект для подписки (типа LiveData или Flowable). Репозиторий периодически обновляет в нем данные. Модель берет этот объект из репозитория и отдает его в Activity, и Activity подписывается на этот объект. Объект теперь хранит ссылку на Activity.
Таким образом получилось, что репозиторий держит ссылку на Activity через объект подписки. И если мы закроем Activity, но не отпишем его от объекта подписки, то возникнет утечка памяти, т.к. репозиторий может жить все время работы приложения. И все это время Activity будет висеть в памяти.
Давайте рассмотрим, как это решается в случае с LiveData или Flowable. Важно понимать, что будет происходить с подпиской при закрытии Activity. ViewModel будем рассматривать только как инструмент передачи объекта из репозитория в Activity.
1) ViewModel готов из репозитория предоставить LiveData. И мы в Activiy хотели бы работать с LiveData.
Цепочка ссылок:
Repository -> LiveData -> Activity
Тут получается полная идилия. Activity берет из модели LiveData, подписывается на него и все ок. При закрытии Activity не будет никаких утечек памяти и прочих проблем с подпиской, т.к. LiveData сам отпишет Activity и тем самым разорвет цепочку ссылок.
2) ViewModel готов вернуть нам Flowable, а мы в Activity хотели бы работать с LiveData.
В этом случае конвертируем Flowable в LiveData внутри модели и отдаем LiveData в Activity.
Цепочка ссылок:
Repository -> Flowable -> LiveData -> Activity
Activity снова будет подписано на LiveData. А это значит, что нам, как и в первом варианте, не надо заботиться об этой подписке. LiveData отпишет от себя Activity, и сам отпишется от Flowable. Цепочка ссылок разорвется в двух местах.
3) ViewModel готов вернуть нам LiveData, а мы в Activity хотели бы работать с Flowable.
В этом случае передаем LiveData в Activity и преобразуем его в Flowable.
Цепочка ссылок:
Repository -> LiveData -> Flowable -> Activity
Activity будет подписано на Flowable. А Flowable будет подписан на LiveData. При этом подписка Flowable на LiveData будет работать с учетом Activity LifeCycle. И когда Activity будет закрыто, LiveData сам отпишет от себя Flowable.
Цепочка ссылок разорвется, но, в любом случае, хорошей практикой является отписка от Flowable вручную при закрытии Activity.
4) ViewModel готов вернуть нам Flowable, и мы в Activity хотели бы работать с Flowable.
В этом случае Activity будет подписано на Flowable.
Цепочка ссылок:
Repository -> Flowable -> Activity
При закрытии Activity нам самим необходимо отписать Activity от Flowable.
Как конвертировать LiveData во Flowable и наоборот, мы рассматривали в Уроке 3.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Использование шаблона MVVM (Model-View-ViewModel) в Android
Автор: Антон Валюх, Senior Mobile Developer.
В этой статье поговорим о теории и практике использования очень удобного шаблона проектирования MVVM (Model-View-ViewModel) при разработке Android-приложений.
MVP — Model-View-Presenter
Для начала — немного теории. Всё началось с того, что многие думали, как приспособить шаблон MVC (Model-View-Controller) для написания приложений с интерфейсом пользователя. И в 2006 г. в работе “GUI Architectures” Мартин Фаулер подробно рассмотрел шаблон, который впоследствии получил название “MVP” (“Model-View-Presenter”).
Итак, MVP — шаблон проектирования, производный от MVC, разработанный в основном для построения пользовательского интерфейса. MVP используется для облегчения автоматического модульного тестирования и улучшения разделения логики и отображения.
В этом шаблоне есть три элемента:
- View.
- Presenter.
- Model (модель).
Вот как это всё работает:
- Элемент View отвечает за показ ползовательских данных и перехват пользовательских действий. Всё это он посылает Presenter.
- Presenter обрабатывает действия пользователя в UI, учитывает изменения данных в Model и посылает эту информацию View. Presenter — это элемент, который содержит всю бизнес-логику работы с пользовательским интерфейсом.
- Model содержит в себе модели из предметной области, которые отображают знания и данные предметной области вашего приложения. Model посылает информацию об изменении данных Presenter и принимает сообщения от Presenter.
MVP — реализация в Android
MVP позволяет создавать абстракцию представления. Для этого необходимо выделить интерфейс представления с определенным набором свойств и методов.
Теперь посмотрим, это можно реализовать в Android — для этого напишем небольшой «велосипед».
Presenter взаимодействует с View путем использования специального интерфейса, который описывает абстракцию этого View.
Допустим, у нас есть вот такая модель View:
Обратите внимание: не нужно путать эту модель View с тем видом (View), который мы видим на экране. View, который используется в MVP — некая абстракция View. Другими словами, это обобщение поведения нашего View. В MVP View не отвечает за то, как именно всё будет отображаться на пользовательском интерфейсе. Она отвечает за то, как будет вести себя пользовательский интерфейс.
Presenter получает ссылку на реализацию интерфейса, взаимодействует с моделью нашего View, инициализирует его, вызывает все его сообщения, посылает ему какие-то сообщения и т. д. Всё взаимодействие происходит напрямую: у нас есть реализация View, мы вызываем ее методы и получаем некий результат.
Другими словами, Presenter как бы подписывается на события View и по необходимости изменяет данные в Model.
В качестве примера View в нашем случае будет выступать Activity, отвечающее за реализацию поведения SomeScreenView. Роль View может играть не только Activity, но Fragment, Dialog или просто Android View. Для этого ему также необходимо реализовать поведение SomeScreenView. В указанном Activity используется объект типа SomeScreenPresenter, который и выступает в роли Presente в нашем примере. Этому объекту мы предоставляем ссылку на реализацию вашего View, которое взаимодействует с Presenter путем прямого вызова у него необходимых методов. В свою очередь, Presenter вызывает методы, реализованные внутри вашей Activity, потому что она является реализацией вашей View.
Этот простой пример демонстрирует, как MVP позволяет декомпозировать логику, которая до этого целиком находилась в Activity и была связана обработкой данных и действий пользователя. Мы вынесли эту логику в отдельный модуль, и этот модуль мы, к примеру, можем проверить, обыкновенным модульным тестированием. С моей точки зрения это намного проще, чем тестирование нашей UI-функциональности с помощью Robotium, запуска эмуляторов и т. д. Другими словами, мы взяли всю нашу логику из Activity, которая до этого была Contrloller, вынесли в новый элемент Presenter, и теперь мы можем этот элемент спокойно протестировать, не создавая никаких Controller и View. Кроме того, это код можно дополнительно улучшить — например, использовать внедрение зависимостей (скажем, с помощью RoboGuice или Dagger).
Шаблон MVP неплох, но Microsoft придумала шаблон еще лучше — MVVM (Model-View-ViewModel). Этот шаблон очень любят .NET-разработчики, он используется в Silverlight, его реализация есть в AngularJS. MVVM — очень удобный шаблон.
Чем отличается MVVM от MVP?
MVVM позволяет связывать элементы View со свойствами и событиями ViewModel. При этом ViewModel — абстракция представления. В MVVM есть:
- View — содержит поля, соответствующие интерфейсу пользователя.
- ViewModel — содержит такие же поля, но в предметной области.
- Собственно, Model.
Свойства View совпадают со свойствами ViewModel/Model. При этом ViewModel не имеет ссылки на интерфейс представления. Изменение состояния ViewModel автоматически изменяет View, и наоборот. Для этого используется механизм связывания данных. Также характерная черта MVVM — двусторонняя коммуникация с View.
Далее я кратко пройдусь по реализациям MVVM под Android, с которыми сталкивался в работе, и рассмотрю достоинства и недостатки каждой. В свое время я отметил для себя три реализации: RoboBinding, ngAndroid, Bindroid. В конце этого обзора кратко остановлюсь на Android Data Binding, который я только начинаю для себя открывать, и который выглядит очень перспективным. Вот, кстати, хороший материал по теме.
RoboBinding
RoboBinding представляет собой MVVM-фреймворк для платформы Android. Он позволяет легко осуществить привязку (binding) атрибутов для любых пользовательских компонентов, сторонних компонентов или виджетов для Android. В итоге можно выкинуть много ненужного кода за счет использования бинов.
RoboBinding — установка
Устанавливать RoboBinding, на мой взгляд, непросто, поскольку он для работы требует Android Annotation Processing Toolkit. Это обусловлено тем, что в основе работы RoboBinding лежит генерации кода на этапе прекомпиляции. При этом код генерируется на основе дополнительных аннотаций, которые содержатся в фреймворке и их нужно чем-либо обработать. Этим и занимается Android Annotation Processing Toolkit.
Честно признаюсь, подключить и настроить APT и RoboBinding у меня получилось раза со второго. Надеюсь, у большинства получится быстрее.
RoboBinding — ViewModel
Так выглядит наша модель представления:
Несмотря на то, что эта модель проаннотирована как “Presentation Model”, это именно ViewModel в концепции шаблона MVVM. Это обыкновенная POJO-модель, которая содержит в себе поля, которые впоследствии будут отображены на вашем View (дальше в примерах будет показано, как это сделать). Чтобы ваши данные отображались в двухстороннем порядке, вам необходимо еще имплементировать интерфейс HasPresentationModelmChangeSupport, где в методе getPresentationModelmChangeSupport вы просто должны вернуть реализацию ChangeSupport’а, который будет менять ваши данные.
В SomeScreenViewModel находятся два поля, которые будут содержать значения отображаемые и получаемые на интерфейсе пользователя, и методы, обеспечивающие доступ к этим полям. Тут же есть метод, который будет отвечать за взаимодействие с пользователем. Как именно это всё будет работать, мы по ходу дела разберемся.
RoboBinding — Layout
В самом View мы используем кастомные атрибуты, чтобы можно было связать поля нашей Model с какими-то конкретными элементами интерфейса. Чтобы всё это заработало, мы подключаем дополнительное пространство имен, после чего указываем кастомные дополнительные поля к нашим элементам интерфейса.
В нашей разметке есть EditText, который с помощью bind:text=»$
«привязывается» к полю private String mUserFirstName нашей SomeScreenViewModel. Теперь любые изменения поля mUserFirstName будут отображены в указанном EditText, а любые изменения данных в этом EditText, будут отображаться в поле mUserFirstName. По такому принципу работает механизм двустороннего связывания данных (data binding) между View и ViewModel.
Кроме того, мы можем обрабатывать данные пользователя. Есть bind:onClick
, содержащий имя метода, а в SomeScreenViewModel есть метод с этим же именем, который будет вызван после нажатия кнопки.
RoboBinding — Activity
Но каким образом Model знает о существовании View, и каким образом View знает о существовании Model? Что именно осуществляет процесс связывания данных? В случае RoboBinding, это класс binder. Ему предоставляется ссылка на Layout, в котором элементы интерфейса содержат кастомные поля, и ему дается ссылка на реализацию Model. После чего binder связывает элементы интерфейса с полями внутри Model. Теперь, воспользовавшись каким-нибудь setter/getter или просто записав в поля данные нашей Model, мы получим их отображение на View.
Всё достаточно просто. При этом, во-первых, нет никаких findViewById. Во-вторых, вы не отвечаете за то, с чем вы конкретно взаимодействуете, — для вас есть только абстрактная модель, в которую вы можете устанавливать значения и получать их оттуда.
Теперь поговорим, как конкретно это работает. Например, пользователь нажал кнопку на интерфейсе пользователя. При этом сработало событие, updateUser, подвязанное к этой кнопке с помощью bind:onClick=»updateUser»
(см. RoboBinding — Layout). Это приводит к вызову связанного с этим действием метода updateUser () в SomeScreenViewModel (см. RoboBinding — ViewModel) Не забываем, что наш SomeScreenViewModel содержит реализацию PresentationModelChangeSupport, о которой мы говорили до этого. Это необходимо, чтобы отображать состояние вашей Model на вашу View.
Со стороны, это можно представить следующим образом:
— Эй, объект типа PresentationModelChangeSupport, возьми поле, которое называется «userFullName» и обнови его! — «говорим» мы в методе updateUser().
— Хорошо, — «думает» объект типа PresentationModelChangeSupport — «userFullName», подвязывается в bind:text=»
. — А есть у меня где-то геттер, который называется «getUserFullName»? Есть. Вызываю его, получаю значение (которое равно mUserFirstName + » » + mUserLastName), и это значение отображаю в
Именно таким образом работает реализация двустороннего связывания данных в RoboBinding.
RoboBinding — преимущества и недостатки
Достоинства RoboBinding:
- двунаправленное (двустороннее) связывание;
- генерация кода;
- поддержка списков.
Недостатки:
- проблемы с библиотекой AppCompat;
- нет поддержки RecyclerView.
Двустороннее связывание: знак “$” в коде означает, что, при изменении данных в Model, они будут отображены во View и, при изменении данных во View, они будут спроецированы в Model. Если же знака “$” нет, это значит, что данные из Model будут отображены во View, но не наоборот.
Работа RoboBinding основана на генерации кода. То есть на этапе предварительной компиляции — на основе аннотаций, что вы расписали в классе, будет сгенерирован необходимый код, после чего он будет скопмилирован. Это значит, что на этапе выполнения никаких дополнительных затрат вам не потребуется.
Кроме того что вы можете связывать простые данные, вы можете связывать и списки (коллекции), а это здорово. Кроме того, есть поддержка работы с ListView: вы можете завязать какую-либо коллекцию на ListView, и всё будет хорошо работать (по крайней мере, у меня работало).
Что касается недостатков, если RoboBinding со списками работает, то с RecyclerView — нет, по крайней мере, пока еще.
Также есть проблема с библиотекой совместимости, потому что связывание построено на кастомных атрибутах. Это значит, что если у нас идут элементы интерфейса из библиотеки совместимости, и вы в неё пытаетесь добавить кастомный атрибут — это работает не всегда, а если и работает, то очень плохо. На сайте RoboBinding’а все эти баги уже отмечены — наверняка работа над их исправлением уже идёт, так как RoboBinding развивается, и развивается достаточно быстро.
ngAndroid
Следующая библиотека, которая мне понравилась, — ngAndroid, основанная на идеях JavaScript-фреймворка AngularJS (но только на идеях — никакого JavaScript здесь нет). Работает она очень похоже на RoboBinding.
ngAndroid — установка
В отличие от RoboBinding, ngAndroid устанавливается очень просто, и всё работает с первого раза: compile ‘com.github.davityle:ngandroid:0.0.4’.
ngAndroid — Model
Model практически ничем не отличается — это обыкновенные данные, способы доступа к данным. Здесь нету action — они поддерживаются, но не в таком виде.
ngAndroid — Layout
Layout такой же — отличия минимальные. Точно так же работа построена на основе кастомных атрибутов — подключили нужное нам пространство имён и отобразили модель с именем атрибута внутри этой модели: модель — имя атрибута, модель — имя атрибута… Также есть поддержка событий пользовательского интерфейса, но об этом потом.
ngAndroid — Activity
Различия начинаются в Activity. Рассмотрим пример, в котором есть некая Activity (здесь может быть Fragment, View и т. д.). А в этой Activity используется аннотация @NgScope, чтобы ngAndroid знал, что эта View должна содержать binder. Внедряем ViewModel нашего примера (SomeScreenViewModel) в Activity с помощью аннотации @ NgModel. Вот, собственно говоря, и все: указали View, указали ViewModel. В отличии от RoboBinding, на этом этапе работы внутри ngAndroid включается инжектор, который выполняет необходимое внедрение указанных зависимостей и настраивает их.
Таким образом реализовывается двустороннее связывание. При этом в роли класса, который отвечает за обработку событий, выступает сама Activity. В Activity реализован метод updateUser(), который до этого был привязан в файле с разметкой к кнопке. Из примера также видно, что, в отличии от RoboBinding, в ngAndroid updateUser() находиться в Activity а не в ViewModel.
ngAndroid — преимущества и недостатки
Достоинства:
- двунаправленное связывание;
- проект на стадии активной разработки.
Недостатки:
- нет поддержки списков / RecyclerView;
- используется рефлексия (обещают перейти на генерацию кода);
- проект на стадии активной разработки, поэтому достаточно сырой.
NgAndroid сейчас быстро развивается — кроме моделей и кликов, библиотека поддерживает long-click, change, disable и т. д. Поддерживаемых директив становится всё больше и больше. В то же время, такое быстрое развитие можно рассматривать как недостаток — я бы поостерегся пока использовать ngAndroid на работе.
На данный момент поддерживаются следующие angular-директивы:
- NgMode
- NgClick
- NgLongClick
- NgChange
- NgDisabled
- NgInvisible
- NgGone
- NgBlur
- NgFocus
В целом библиотека очень понравилась — прозрачностью, простотой, тем, что есть внедрение зависимости (меньше кода, и это хорошо), тем, что есть двустороннее (двунаправленное) связывание.
Bindroid
Bindroid — еще одна реализация шаблона MVVM для Android-приложений. Bindroid представляет собой библиотеку с открытым исходным кодом, основная целью которой — упрощение связывания пользовательского интерфейса и данных. В ее основе лежит шаблон «Наблюдатель» (Observer) для работы с моделями, а также набор методов для быстрого связывания этих объектов и интерфейсов пользователя.
Bindroid — Model
Bindroid в корне отличается от уже рассмотренных реализаций тем, что в нем нет кастомных полей ваших UI-атрибутов, другими словами, нет элемента, который связывает ваш View c полями вашей Model. Вместо этого есть поле TrackableField, которое находится внутри Model — все поля данных должны быть TrackableField’ом. Это сделано, чтобы, когда вы измените какое-либо поле, оно изменилось на вашей View. Таким образом, здесь реализован шаблон Observer, который следит, чтобы изменения данных отображались в UI.
Bindroid — Layout
К сожалению, для связывания необходимо вручную связать каждое поле из Model с конкретной его реализацией внутри вашей View, используя findViewById. Хотя от findViewById можно избавиться, используя ButterKnife или Android Annotations.
Bindroid — Activity
Bindroid — преимущества и недостатки
Достоинства:
- двунаправленное связывание;
- поддерживает работу с библиотекой AppCompat.
Недостатки:
- нет поддержки генерации кода;
- нет проверки во времени компиляции;
- слишком много кода для связывания.
Bindroid может показаться вам интересным, если вы не любите кастомные поля у элементов интерфейса или внедрение зависимости, или если вам не нравится переключать лишние инструменты вроде Android Annotation Processing Toolkit, которые потребляют дополнительные ресурсы. Или, может быть, вам нужно, чтобы всё быстро компилировалось и работало. Тогда Bindroid вам подойдет, но надо будет писать код чуть подольше.
На мой взгляд, большой недостаток — отсутствие проверки на этапе компиляции. Например, если в Model поле называется userLastName, а в Activity вы допустите ошибку, всё у вас скомпилируется, но в процессе выполнения произойдет exception. И, поскольку Stack Trace будет у вас очень веселым при выполнении связывания, будете очень долго искать, что не так. Это — серьезный недостаток.
Android Data Binding
Весной 2015 г. Google на Google I/O представил библиотеку Android Data Binding, пока что доступную в бета-версии. Возможностей у нее много, но в в статье расскажу о ее возможностях, связанных с MVVM.
Android Data Binding -—установка
Установка достаточно простая. Здесь стоит сказать, что, поскольку Android Data Binding находится в стадии бета-тестирования, Android Studio пока не поддерживает нормальную работу с (июль 2015, Android Studio v 1.3.0).
Android Data Binding — Model
В Model ничего необычного нет — у нас есть те же самые поля и есть методы доступа к этим полям.
Android Data Binding — Layout
Что касается файла разметки, нашего View, тут уже есть серьезные отличия от рассмотренных ранее реализаций. Во-первых, корневой узел теперь у нас — так называемый Layout. В разделе data указывается модель и то, как она будет называться (пространство имен). А дальше происходит отображение данных из UI на поля указанной модели (в данном случае это user.fullName, user.firstName и user.lastName
— соответственно, те же поля внутри вашей модели).
То есть, как и раньше, у нас есть поля, есть модель и есть механизм связывания, который позволяет отобразить поля вашей модели на элементы UI интерфейса. Разница заключается в том, что корневым узлом у вас является Layout, и, кроме самого Layout, у вас есть ещё секция с данными, где вы должны указать, какую модель вы используете.
Детальней использование можно рассмотреть на следующем примере.
Android Data Binding — Activity
В Activity минимальные изменения: сделали модель данных, связали View и модель данных, после чего, в процессе изменения каких-то значений внутри модели, эти данные будут изменяться внутри View. Если данные будут изменены на View, изменения будут доступны в модели. Для двустороннего связывания между данными и местом их отображения в UI используется символ «@» (например, android:text=»@
). В противном случае, реализация связывания получиться односторонней.
Таким образом, использование Data Binding, с моей точки зрения, выглядит достаточно простым и прозрачным, и является реализацией шаблона MVVM.
Android Data Binding — возможности
Кроме того, что в Data Binding есть возможность реализовать шаблон MVVM, у этой технологии есть еще много хороших возможностей (на самом деле, Android Data Binding — тема для отдельного доклада или статьи).
Язык выражений. Позволяет писать примитивную логику внутри вашей View. Главное — не переусердствовать, чтобы логика не перешла во View. Тем не менее, язык выражений позволяет делать упрощения — в зависимости от состояния, вы можете подхватывать разные обработчики, делать форматирование. Это очень удобно.
Импорты. Можно дополнительно импортировать любые классы: к примеру, импортировали View, и дальше можете свойство этого класса использовать в каких-то своих выражениях.
Поддержка ресурсов. Написали выражение, указали, какие ресурсы брать, и всё прекрасно работает.
Можно создавать кастомные бины.
Можно долго перечислять — много чего еще есть.
Наверняка рано или поздно Android Data Binding станет новым стандартом создания Android-приложений — я в этом уверен почти на все 100.
Android Data Binding — преимущества и недостатки
Достоинства:
- официальная библиотека от Google;
- генерация кода;
- проверка во время компиляции;
- простота в использовании и расширении;
- новый Android-стандарт.
Недостатки:
- нет поддержки двунаправленного связывания (пока еще);
- нет поддержки IDE (пока еще);
- много ложных ошибок в Android Studio (но все компилируется и запускается).
Важный недостаток — отсутствие полноценного двунаправленного связывания — оно есть, но пока работает плохо. Ну, что же взять с бета-версии… наверняка, это первое, что будет полноценно работать в ближайшем будущем.
Нет поддержки IDE — как результат, очень много ошибок в Android Studio. Но всё компилируется, всё запускается, всё работает. Если кому-то интересно и хотите подключить, думаю, не пожалеете.
Источник