Android architecture components data binding

Урок 18. Android Data Binding. Основы

В этом уроке знакомимся с Data Binding.

Полный список уроков курса:

В build.gradle файл модуля в секции android необходимо включить Data Binding:

Правильно произносить байндинг , но биндинг звучит проще — буду использовать его.

Data Binding поможет организовать работу с View так, чтобы нам не пришлось писать кучу методов findViewById, setText, setOnClickListener и т.п. Давайте рассмотрим простой пример.

Есть класс Employee, который содержит в себе данные о работнике

поля: id, имя и адрес.

Мы хотим вывести имя и адрес работника на экран main_activity.xml:

Несложная задача. Для этого мы обычно пишем методы findViewById и setText. Тут все понятно.

Давайте рассмотрим, как это же можно сделать с помощью Data Binding.

Вносим изменения в main_activity.xml:

Корневым элементом теперь является , а LinearLayout сместился внутрь него.

В секции data мы объявляем переменную с именем employee. Ее тип — ранее рассмотренный класс Employee. Теперь мы можем использовать эту переменную в атрибутах вьюшек этого layout. В первом TextView, в атрибуте text мы используем employee.name, а в втором TextView — employee.address.

Обратите внимание, что мы не указываем id для View. В этом нет необходимости.

Как вы понимаете, нам остается лишь передать объект Employee в этот layout. И значения этого объекта будут подставлены в соответствующие TextView.

Это делается следующим образом.

Сначала создаем Employee объект.

Затем используем DataBindingUtil. Метод DataBindingUtil.setContentView внутри себя сделает привычный нам setContentView для Activity, а также настроит и вернет объект биндинга MainActivityBinding.

MainActivityBinding — это сгенерированный класс. Имя этого класса берется из имени layout файла (т.е. main_activity), плюс слово Binding. MainActivityBinding все знает о нашем layout: какие View там есть, какие переменные (variable) мы там указывали, и как все это связать друг с другом, чтобы данные из переменных попадали в View.

Метод setEmployee был сгенерирован в классе биндинга, т.к. мы описали переменную employee в layout файле. Этим методом мы передаем биндингу объект Employee. Биндинг возьмет значения employee.name и employee.address и поместит их (методом setText) в соответствующие TextView. Все, как мы и настраивали в layout.

Данные из Employee помещены в TextView.

Сразу хочу заметить, что если мы теперь в коде будем изменять объект Employee, то данные на экране меняться не будут. Они считались один раз и далее не отслеживаются (при такой реализации).

Чтобы экран получил новые данные, надо снова передать биндингу измененный объект Employee:

Или можно вызывать метод invalidateAll:

Биндинг считает новые данные с ранее полученного объекта Employee.

Поля в Employee в этом примере я сделал public. Но вы можете сделать их private и создать для них public get методы.

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

Кроме того, мы рассмотрели совсем простой случай использования биндинга. Но его возможности гораздо шире. Например, можно сделать так, чтобы при передаче в атрибут ImageView ссылки на картинку, биндинг запускал Picasso для загрузки этой картинки и помещал результат в ImageView. Или биндинг может сам отслеживать изменения в объекте с данными (в примере выше — это Employee ) и обновлять экран без каких-либо дополнительных методов.

Эти возможности мы рассмотрим в следующих уроках.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

Читайте также:  Поддерживает ли андроид ntfs флешки

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Android Architecture Components в связке с Data Binding

Не так давно для андроид-разработчиков Google представил новую библиотеку — Android Architecture Components. Она помогает реализовать в приложении архитектуру на основе паттернов MVx (MVP, MVVM etc.). Кроме того, уже давно выпущена другая библиотека от Google — Data Binding Library. Она позволяет прямо в разметке связывать отображение UI-контролов со значениями, содержащимися в объектах. Это важная особенность паттерна MVVM — связывать слой View со слоем ViewModel.

Обе библиотеки направлены на построение архитектуры Android-приложений в MVVM стиле.
Я расскажу, как можно использовать их вместе для создания проекта с архитектурой на основе MVVM.

Немного об MVVM

Паттерн MVVM предполагает разделение архитектуры приложения на 3 слоя:

  • Model — слой данных. Содержит всю бизнес-логику приложения, доступ к файловой системе, базе данных, ресурсам системы и другим внешним сервисам;
  • View — слой отображения. Все, что видит пользователь и с чем может взаимодействовать. Этот слой отображает то, что представлено в слое ViewModel. Также он отправляет команды (например, действия пользователя) на выполнение в слой ViewModel;
  • ViewModel — слой представления. Связан со слоем View байндингами. Содержит данные, которые отображены на слое View. Связан со слоем Model и получает оттуда данные для отображения. Также обрабатывает команды, поступающие из слоя View, изменяя тем самым слой Model.


Основной интерес в статье будет прикован к байндингам. Это связи отображения конкретных параметров View (например, “text” у TextView) с конкретными полями представления ViewModel (например, поле “имя пользователя”). Задаются они в разметке View (в layout), а не в коде. ViewModel, в свою очередь, должна так представлять данные, чтобы их было легко связать байндингами с View.

Зачем нам это все надо?

Сам по себе паттерн MVVM, как и MVP, и MVC, позволяет разделить код на независимые слои. Основное отличие MVVM — в байндингах. То есть, в возможности прямо в разметке связать отображение того, что видно пользователю — слой View, с состоянием приложения — слоем Model. В общем, польза MVVM в том, чтобы не писать лишний код для связывания представления с отображением — за вас это делают байндинги.

Google двигается в сторону поддержки архитектуры на основе паттерна MVVM. Библиотеки Android Architecture Components (далее, AAC) и Data Binding — прямое тому подтверждение. В будущем, скорее всего, этот паттерн будет использоваться на большинстве проектах под Android.

На данный момент проблема в том, что ни AAC, ни Data Binding не предоставляет возможности реализовать MVVM паттерн в полной мере. AAC реализует слой ViewModel, но байндинги надо настраивать вручную в коде. Data Binding, в свою очередь, предоставляет возможность написать байндинги в разметке и привязать их к коду, но слой ViewModel надо реализовывать вручную, чтобы прокидывать обновление состояния приложения через байндинги к View.

В итоге, вроде бы, все уже готово, но разделено на две библиотеки, и чтобы это было действительно похоже на MVVM, нужно просто взять и объединить их.

В общем, что надо для этого сделать:

  • реализовать слой View на байндингах;
  • реализовать слой ViewModel на основе классов LiveData и ViewModel из AAC;
  • связать эти два слоя минимальным количеством кода;
  • оформить это так, чтобы это можно было переиспользовать в проектах.

Сделать это попробуем на примере простого экрана профиля пользователя.

Описание примера

На экране будет три элемента:

  • кнопка войти/выйти. Текст зависит от того, авторизован пользователь или нет;
  • поле ввода логина. Показывается, когда пользователь не авторизован;
  • лэйбл с логином. Показывает логин авторизованного пользователя.
Читайте также:  Объединить 2 фотки андроид

Логин будет храниться в SharedPreferences. Пользователь считается авторизованным, если в SharedPreferences записан какой-нибудь логин.

Для простоты не будут использоваться сторонние фреймворки, запросы к сети, а также отображение ошибок.

Слой View

Начну я со слоя View, чтобы было понятно, что будет видно пользователю на экране. Сразу же размечу нужные мне байндинги без привязки к конкретной ViewModel. Как это все будет работать — станет понятно позже.

Класс LiveData

Перед реализацией слоя Model надо разобраться с классом LiveData из AAC. Он нам понадобится для нотификации слоя ViewModel об изменениях слоя Model.

LiveData — это класс, объекты которого поставляют данные и их обновления подписчикам. Он представляет собой реализацию паттерна Observer. На LiveData можно подписаться, а сама LiveData реализует внутри то, как она будет вычислять и обновлять данные для подписчиков.

Особенность LiveData в том, что она может быть привязана к объекту жизненного цикла и активироваться, только когда такой объект в состоянии started. Это удобно для обновления слоя View: пока активити или фрагмент в состоянии started, это значит, что у них инициализирован весь UI и им нужны актуальные данные. LiveData реагирует на это и активизируется — рассчитывает актуальное значение и уведомляет подписчиков об обновлении данных.

Слой Model

От слоя Model нам нужна следующая функциональность: методы login(String login), logout() и возможность отслеживать текущий логин авторизованного пользователя на основе LiveData.

Добавим класс ProfileRepository, который будет отвечать за логику авторизации пользователя:

Этот объект разместим в Application, чтобы было проще получить к нему доступ, имея Context:

Класс ViewModel

Перед реализацией слоя ViewModel надо разобраться с основным классом из AAC, использующимся для этого.

ViewModel — это класс, представляющий объекты слоя ViewModel. Объект такого типа может быть создан из любой точки приложения. В этом классе всегда должен быть либо дефолтный конструктор (класс ViewModel), либо конструктор с параметром типа Application (класс AndroidViewModel).

Чтобы запросить ViewModel по типу, нужно вызвать:

ViewModel хранятся отдельно для каждой активити и для каждого фрагмента. При первом запросе они создаются и помещаются для хранения в активити или фрагменте. При повторном запросе — возвращается уже созданная ViewModel. Уникальность конкретной ViewModel — это ее тип или строковый ключ + где она хранится.

Создаются ViewModel и AndroidViewModel по умолчанию через рефлексию — вызывается соответствующий конструктор. Так что, при добавлении своих конструкторов, в методе ViewModelProviders.of(. ) нужно явно указывать фабрику создания таких объектов.

Слой ViewModel

От ProfileViewModel нам надо следующее:

  • метод loginOrLogout, который будет представлять команду логина или логаута пользователя, в зависимости от того, авторизован ли пользователь;
  • изменяемое значение isUserLoggedIn, которое будет представлять состояние — авторизован ли пользователь;
  • изменяемое значение loggedInUser, которое будет представлять логин текущего авторизованного пользователя;
  • изменяемое значение inputLogin, которое будет представлять то, что пользователь ввел на экране в поле логина.

Создадим ProfileViewModel и свяжем ее с ProfileRepository:

Теперь при вызове метода loginOrLogout в ProfileRepository будет обновляться LoginLiveData и эти обновления можно будет отображать на слое View, подписавшись на LiveData из ProfileViewModel.

Но LiveData и ViewModel пока что не адаптированы под байндинг, так что использовать этот код еще нельзя.

Адаптация ViewModel под Data Binding

С доступом к ViewModel из разметки проблем особых нет. Объявляем ее в разметке:

И устанавливаем в активити или фрагменте:

Адаптация LiveData под Data Binding

Адаптировать LiveData я решил на основе класса ObservableField. Он позволяет привязать изменяющееся значение произвольного типа к конкретному свойству view.

В моем примере надо будет прибайндить visibility у view к тому, авторизован пользователь или нет. А также свойство text к логину пользователя.

У ObservableField есть два метода — addOnPropertyChangedCallback и removeOnPropertyChangedCallback. Эти методы вызываются, когда добавляется и удаляется байндинг из view.
По сути, эти методы — те моменты, когда нужно подписываться и отписываться от LiveData:

Читайте также:  Смартфон с андроидом для чайников

Для подписки на LiveData я использовал метод observeForever. Он не передает объект жизненного цикла и активирует LiveData независимо от того, в каком состоянии находятся активити или фрагмент, на котором находятся view.

В принципе, из объекта OnPropertyChangedCallback можно достать view, из view — context, context привести к LifecycleActivity и привязать LiveData к этой активити. Тогда можно будет использовать метод observe(lifecycleObject, observer). Тогда LiveData будет активироваться только когда активити, на которой находится view, в состоянии started.

Выглядеть этот хак будет примерно так:

Теперь изменим ProfileViewModel так, чтобы к ней можно было легко прибайндиться:

Важно! В процессе тестирования обнаружился один неприятный недостаток в библиотеке Data Binding — прибайнденные view не вызывают метод removeOnPropertyChangedCallback даже когда активити умирает. Это приводит к тому, что слой Model держит ссылки на объекты слоя View через слой ViewModel. В общем, утечка памяти из объектов LiveDataField.

Чтобы этого избежать, можно использовать еще один хак и вручную обнулить все байндинги на onDestroy активити:

Кроме того, внимательные читатели могли заметить в разметке класс SafeEditText. В общем, он понадобился, из-за бага в Data Binding Library. Суть в том, что она добавляет в листенер вводимого текста через addTextChangedListener даже если этот листенер null.
Так как на этапе onDestroy я обнуляю модель, то сперва в EditText добавляется null-листенер, а потом обновляется текст, который стал тоже null. В итоге на onDestroy происходил NPE краш при попытке оповестить null-листенер о том, что текст стал null.

В общем, при использовании Data Binding будьте готовы к таким багам — их там довольно много.

Не идеально, но получилось

В общем, с некоторыми трудностями, хаками и некоторыми разочарованиями, но связать AAC и Data Binding получилось. Скорее всего, в скором времени (года 2?) Google добавит какие-нибудь фичи, чтобы связать их — тот же аналог моей LiveDataField. Пока что AAC в альфе, так что там многое еще может измениться.

Основные проблемы на текущий момент, на мой взгляд, связаны с библиотекой Data Binding — она не подстроена под работу с ViewModel и в ней есть неприятные баги. Это наглядно видно из хаков, которые пришлось использовать в статье.

Во-первых, при байндинге сложно получить активити или фрагмент, чтобы получить LifecycleObject, необходимый для LiveData. Эту проблему можно решить: либо достаем это через рефлексию, либо просто делаем observeForever, который будет держать подписку на LiveData, пока мы вручную не обнулим байндинги на onDestroy.

Во-вторых, Data Binding предполагает, что ObservableField и прочие Observable объекты живут тем же жизненным циклом, что и view. По факты эти объекты — это часть слоя ViewModel, у которой другой жизненный цикл. Например, в AAC этот слой переживает перевороты активити, а Data Binding, не обновляет байндинги после переворота активити — для нее все view умерли, а значит, и все Observable объекты тоже умерли и обновлять ничего нет смысла. Эту проблему можно решить обнулением байндингов вручную на onDestroy. Но это требует лишнего кода и необходимости следить, что все байндинги обнулены.

В-третьих, возникает проблема с объектами слоя View без явного жизненного цикла, например, ViewHolder адаптера для RecyclerView. У них нет четкого вызова onDestroy, так как они переиспользуются. В какой момент обнулять байндинги во ViewHolder — сложно сказать однозначно.

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

Пример из статьи можно посмотреть на гитхабе Touch Instinct.

Источник

Оцените статью