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

Продолжаем изучать 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. Полная документация по этому вопросу здесь.

Более подробно на практике мы работаем с LiveData и Android Data Binding в новом продвинутом курсе

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

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

val name = "Grace"
    val lastName = "Hopper"
    var likes = 0
        private set // This is to prevent external modification of the variable.

таким кодом:

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 все приватные методы и их вызовы. Код в активити теперь максимально прост:

// Obtain ViewModel from 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 для создания пользовательских атрибутов.

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

До встречи на следующем уроке, всем добра.

Урок 10. Android Data Binding. Binding Adapters

Коментарі: 2
  1. Vladimir Chernenko

    По результатам 9 урока перестали работать ProgressBar и ImageView. Так было задумано?
    И будет ли 10 урок?

    1. admin (автор)

      Да, в следующем уроке будет исправлено

Додати коментар