March 15, 2020

Android Data Binding – события UI и наблюдаемые данные

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

User events

В этой статье мы рассмотрим обработку пользовательских событий ввода и работу с наблюдаемыми данными (observable data), при изменении которых будет меняться их представление.

Будем использовать рассмотренный ранее проект, скачать его можно здесь.

Изменим макет разметки главного экрана activity_main.xml. Во-первых, заменим переменные для ViewModel:

<data>
    <variable
        name="viewmodel"
        type="info.fandroid.databindingsample.data.SimpleViewModel"/>
</data>

Таким образом, код представления и состояние будут содержаться в одном месте. Вместо прямого доступа к переменным мы будем вызывать свойства viewmodel.

Теперь измените выражения макета в обоих текстовых полях:

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

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

Также мы будем реагировать на нажатия на кнопку лайков. Найдите кнопку like_button и замените:

android:onClick="onLike"

этим кодом:

android:onClick="@{() -> viewmodel.onLike()}"

В предыдущем атрибуте onClick использовался небезопасный механизм, при котором метод onLike() в активити или фрагменте вызывается при щелчке по представлению. Если указать ошибочное имя метода, среда разработки не заметит ошибку, но приложение вылетит.

Новый способ намного безопаснее, поскольку он проверяется во время компиляции и использует лямбда-выражение для вызова метода onLike() модели представления.

Проверьте наличие ошибок привязки данных, нажав «Make Project» в меню «Build» в Android Studio. Вы увидите, что в процессе сборки проекта появятся ошибки, которые будут показаны в журнале сборки. Клик на ошибке приведет вас в MainActivity, где идет обращение к несуществующим переменным макета.

Давайте удалим из проекта то, что нам уже не нужно.

1. Замените строки в MainActivity:

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

на это:

binding.viewmodel = viewModel

Повторная команда создания проекта должна выполниться успешно.

2. Удалите метод onLike в MainActivity, так как он теперь не нужен.

Если вы запустите приложение, то увидите, что кнопка ничего не делает. Это потому, что мы больше не вызываем updateLikes(). Давайте реализуем это правильно.

Observing data

Мы создали статическую привязку на предыдущем шаге. Если вы откроете модель представления (класс SimpleViewModel), вы обнаружите, что val name и val lastName — это неизменяемые строковые переменные, поскольку их не нужно менять. Однако количество лайков var likes должно изменяться в ответ на действия пользователя. Вместо явного обновления пользовательского интерфейса при изменении этого значения мы сделаем его наблюдаемым — observable. Таким образом, при изменении наблюдаемого значения элементы пользовательского интерфейса будут обновляться автоматически.

Есть несколько способов реализации наблюдаемости. Вы можете использовать наблюдаемые классы, наблюдаемые поля или, предпочтительно, LiveData. Полная документация по этому вопросу здесь.

Мы рассмотрим наблюдаемые поля (ObservableFields), поскольку они проще.

Замените этот код:

val name = "Grace"
val lastName = "Hopper"
var likes = 0
    private set //Для предотвращения изменения переменной вне класса

таким кодом:

private val _name = MutableLiveData("Ada")
private val _lastName = MutableLiveData("Lovelace")
private val _likes = MutableLiveData(0)
val name: LiveData<String> = _name
val lastName: LiveData<String> = _lastName
val likes: LiveData<Int> = _likes

Класс MutableLiveData является расширением LiveData, здесь он используется как хелпер для тех случаев, когда мы не хотим помещать логику обновления значения в LiveData, а лишь хотим использовать его как Holder.

Также замените этот код:

fun onLike() {
    likes++
}

/**
 * Returns popularity in buckets: [Popularity.NORMAL],
 * [Popularity.POPULAR] or [Popularity.STAR]
 */
val popularity: Popularity
    get() {
        return when {
            likes > 9 -> Popularity.STAR
            likes > 4 -> Popularity.POPULAR
            else -> Popularity.NORMAL
        }
    }

таким кодом:

// popularity is exposed as LiveData using a Transformation
// instead of a @Bindable property.
val popularity: LiveData<Popularity> = Transformations.map(_likes) {
    when {
        it > 9 -> Popularity.STAR
        it > 4 -> Popularity.POPULAR
        else -> Popularity.NORMAL
    }
}

fun onLike() {
    _likes.value = (_likes.value ?: 0) + 1
}

Как вы можете видеть, значение LiveData должно быть установлено с помощью setValue(), и мы можем сделать одну LiveData зависимой от другой, используя Transformations. Этот механизм позволяет библиотеке обновлять пользовательский интерфейс при изменении значения.

LiveData поддерживает события жизненного цикла, поэтому вам нужно указать, какой lifecycle owner использовать. Вы делаете это в объекте привязки.

В MainActivity установите lifecycle owner в binding object:

binding.lifecycleOwner = this

Удалите из MainActivity все приватные методы и их вызовы. Код в активити теперь максимально прост:

// Получим ViewModel из ViewModelProviders
private val viewModel by lazy {
    ViewModelProviders.of(this).get(SimpleViewModel::class.java)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding: PlainActivityBinding =
        DataBindingUtil.setContentView(this, R.layout.plain_activity)
    binding.viewmodel = viewModel
}

Удаление лишнего кода из активити отлично способствует удобству сопровождения и тестирования.

Давайте свяжем TextView, показывающий количество лайков, с наблюдаемым значением int. В макете activity_main.xml:

<TextView
    android:id="@+id/likes"
    android:text="@{Integer.toString(viewmodel.likes)}"
    .../>

Если вы запустите приложение сейчас, количество лайков будет увеличиваться при нажатии, как и ожидалось.

Резюме

Давайте вспомним, что мы сделали до сих пор:

  1. Имя и фамилия отображаются в виде строк из модели представления.
  2. Атрибут кнопки onClick привязан к модели представления с помощью лямбда-выражения.
  3. Количество лайков отображается в модели представления через наблюдаемое значение int и привязывается к текстовому представлению, поэтому оно автоматически обновляется при его изменении.

До сих пор мы использовали такие атрибуты, как android: onClick и android: text. В следующей статье рассмотрим другие свойства и создадим свои собственные атрибуты, а также используем Binding Adapters для создания пользовательских атрибутов.

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

Источник: Урок 9. Android Data Binding с событиями пользовательского интерфейса и наблюдаемыми данными