March 12, 2020

Android Data Binding – основы

Data Binding Library

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

В этой серии статей (ссылка на источник – в конце статьи) вы узнаете, как настроить Data Binding в проекте, что такое layout expressions, как работать с observable objects и как создавать кастомные Binding Adapters, чтобы свести избыточность вашего кода к минимуму.

С чего начать

Мы возьмем уже существующий проект и изменим его, добавив Data Binding:

Приложение имеет один экран, который показывает некоторые статические данные и некоторые наблюдаемые данные, что означает, что при изменении данных пользовательский интерфейс будет автоматически обновляться. Данные предоставлены ViewModel. Model-View-ViewModel — это шаблон уровня представления, который очень хорошо работает с Data Binding. Вот диаграмма паттерна MVVM:

Если вы еще не знакомы с классом ViewModel из библиотек компонентов архитектуры, вы можете посмотреть официальную документацию. Это класс, который предоставляет состояние пользовательского интерфейса для представления (Activity, Fragment и т.п.). Он выдерживает изменения ориентации и действует как интерфейс для остальных слоев вашего приложения.

Что нам понадобится

  • Среда разработки Android Studio 3.4 или более новой версии.
  • Приложение без Data Binding.

На этом этапе мы загрузим и запустим простое приложение-пример. Выполните в консоли команду:

git clone https://github.com/androidstart/DataBindingSample/tree/master

Вы также можете склонировать или скачать репозиторий в виде Zip-файла по ссылке на GitHub.

  1. Распакуйте проект.
  2. Откройте проект в Android Studio версии 3.4 или выше.
  3. Запустите приложение.

Экран по умолчанию выглядит так:

Этот экран отображает несколько различных полей и кнопку, по нажатии на которую можно увеличить счетчик, обновить индикатор выполнения и изображение. Логика действий, происходящих на экране, описана в классе SimpleViewModel. Откройте его и посмотрите.

Используйте Ctrl + N, чтобы быстро найти класс в Android Studio. Используйте Ctrl + Shift + N, чтобы найти файл по его имени. На Mac найдите класс с помощью Command + O и файл с помощью Command + Shift + O.

В классе SimpleViewModel описаны поля:

  • Имя и фамилия
  • Количество лайков
  • Уровень популярности

Кроме того, он позволяет пользователю увеличивать количество лайков методом onLike().

Пока SimpleViewModel содержит не самый интересный функционал, но здесь все в порядке. С другой стороны, класс главного экрана MainActivity имеет ряд проблем:

  • Он вызывает метод findViewById несколько раз. Это не только медленно, но и небезопасно, потому что не проверяется на ошибки во время компиляции. Если идентификатор, который вы передаете в findViewById, неправильный, приложение аварийно завершит работу во время выполнения.
  • Устанавливает начальные значения в onCreate. Было бы намного лучше иметь значения по умолчанию, которые устанавливаются автоматически.
  • Использует в макете атрибут android:onClick, который также не является безопасным: если метод onLike не реализован в активити (или переименован), приложение упадет в рантайме.
  • В нем много кода. Активити и фрагменты имеют тенденцию расти очень быстро, поэтому желательно удалить из них как можно больше кода. Кроме того, код в активити и фрагментах трудно тестировать и поддерживать.

В этой серии статей с помощью библиотеки Data Binding мы собираемся исправить все эти проблемы, переместив логику из активити в места, где ее можно повторно использовать и проще тестировать.

Подключаем Data Binding в проект

Первым шагом является включение библиотеки Data Binding в модули, которые будут ее использовать. Добавьте такие строки в файл сборки build.gradle модуля app:

android {
    ...
    dataBinding {
        enabled true
    }
}

Конвертируем макет в Data Binding

Откройте файл activity_main.xml. Это обычный макет с Constraint Layout в качестве корневого элемента.

Для того чтобы преобразовать макет в Data Binding, необходимо:

  • Обернуть ваш макет тегом <layout>
  • Добавить переменные макета (layout variables)
  • Добавить выражения макета (layout expressions)

Обернем макет в тег <layout>

Обернем корневой элемент в тег <layout>. Нам также придется переместить определения пространства имен (атрибуты, которые начинаются с xmlns:) в новый корневой элемент.

Очень удобно, что Android Studio предлагает способ сделать это автоматически:

Теперь ваш макет должен выглядеть примерно так:

<layout  xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

...
</layout>

Между тегами <data> мы разместим layout variables.

Layout variables и layout expressions

Переменные макета layout variables используются для записи выражений макета layout expressions. Layout expressions помещаются в значение атрибутов элемента. Для них используют формат записи @{expression} .

Вот несколько примеров:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}' 

Язык layout expressions довольно мощный, но вы должны использовать его только для базовой разметки, потому что более сложные выражения затруднят чтение и поддержку.

// Биндим свойство name нашей viewmodel к текстовому атрибуту
android:text="@{viewmodel.name}"
// Биндим свойство nameVisible нашей viewmodel к атрибуту visibility
android:visibility="@{viewmodel.nameVisible}"
// Вызываем метод onLike() у viewmodel, когда нажимают на view.
android:onClick="@{() -> viewmodel.onLike()}"

С полным описанием языка можно ознакомиться здесь.

Теперь давайте привяжем некоторые данные.

Создадим layout expression

Начнем с привязки статических данных. Создадим две String layout variables внутри тега <data>.

<data>
    <variable name="name" type="String"/>
    <variable name="lastName" type="String"/>
</data>

Найдем TextView по ID plain_name и добавим атрибут android:text с layout expression:

<TextView
    android:id="@+id/plain_name"
    android:text="@{name}"
    .../>

Layout expressions начинаются с символа @ и заключены в фигурные скобки { }.

Поскольку переменная name имеет тип String, Data Binding будет знать, как установить это значение в TextView. Позже вы узнаете, как обращаться с различными типами и атрибутами layout expressions.

Сделайте то же самое с текстовым полем plain_lastName:

<TextView
    android:id="@+id/plain_lastname"
    android:text="@{lastName}"
    .../> 

Теперь нам нужно изменить Activity, чтобы она правильно наполняла макет Data Binding.

Изменим Activity

Макет готов, но нам нужно внести некоторые изменения в активити. Откроем MainActivity.

Поскольку мы используем Data Binding layout, наполнение макета происходит другим путем.

В onCreate замените код определения макета разметки:

setContentView(R.layout.activity_main)

этим кодом:

val binding: ActivityMainBinding =
    DataBindingUtil.setContentView(this, R.layout.activity_main)

Почему мы создаем переменную? Потому что нам нужен способ установить переменные макета, которые мы объявили в нашем блоке <data>. Вот для чего предназначен binding object. Связывающие классы создаются библиотекой автоматически. Откройте сгенерированный класс ActivityMainBinding и посмотрите его код, он не сложен. Здесь поля, которые соответствуют элементам разметки, конструкторы, методы доступа, а также методы, которые инфлейтят и привязывают view-компоненты. Имя этого класса формируется автоматически из имени layout файла разметки (т.е. activity_main) плюс слово Binding. ActivityMainBinding все знает о нашей разметке: какие View там есть, какие переменные (variable) мы там указывали и как все это связать друг с другом, чтобы данные из переменных попадали во View.

Теперь нам просто нужно установить значения переменных:

binding.name="Your name"
binding.lastName="Your last name"

И это все. Мы просто связали данные с помощью библиотеки.

Теперь мы можем удалить ненужный код:

  • Удалите метод updateName(). Data Binding автоматически находит поля по идентификаторам и устанавливает значения.
  • Удалите вызов метода updateName() из onCreate().

Теперь вы можете запустить приложение. Имя и фамилия должны измениться.

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

Исходный код измененного проекта здесь.

Источник: Урок 8. Android Data Binding – основы