В этом уроке разберем, как настроить автоматическую передачу данных в View и обратно.

 


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


 

Когда мы используем биндинг для обычного Java объекта, то экран не будет автоматически меняться при изменении значений в этом объекте.

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

В первом уроке по Data Binding я упоминал, что есть возможность сделать так, чтобы биндинг сам мониторил значения полей и обновлял экран, как только произошли какие-то изменения. Для этого надо использовать механизмы Observable.

 

 

 

Observable поля 

Сделаем несколько Observable полей в классе Employee:

public class Employee {

   public Employee(long id, String name, String address, int salary) {
       this.id = id;
       this.name.set(name);
       this.address = address;
       this.salary.set(salary);
   }

   public long id;

   public ObservableField<String> name = new ObservableField<>();

   public String address;

   public ObservableInt salary = new ObservableInt();

}

Для Java примитивов есть готовые Observable поля: ObservableInt, ObservableFloat и т.п. Для остальных используем ObservableField с указанием типа. Чтобы присвоить такому полю значение, используем метод set.

Поля name и salary делаем Observable. Их мы будем использовать в биндинге.

 

layout файл выглядит как обычно, в нем ничего менять не надо:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

   <data>
       <variable
           name="employee"
           type="ru.startandroid.application.data.Employee"/>
   </data>

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{employee.name}" />

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{String.valueOf(employee.salary)}"/>
   </LinearLayout>

</layout>

 

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

employee.name.set("Mark");
employee.salary.set(20000);

А биндинг сам отследит эти изменения и обновит экран.

 

Для коллекций есть классы ObservableArrayMap и ObservableArrayList.

Биндинг будет работать, даже если передавать значения в Observable поля не в UI потоке.

 

 

Рассмотрим еще один возможный сценарий использования ObservableField. Его можно использовать не только с отдельными полями объекта, но и с целым объектом. 

Есть класс Employee:

public class Employee {

   public Employee(long id, String name, String address, int salary) {
       this.id = id;
       this.name = name;
       this.address = address;
       this.salary = salary;
   }

   public long id;

   public String name;

   public String address;

   public int salary;

}

Вполне может быть ситуация, когда нам в ViewModel (или в презентер) периодически "прилетает" из репозитория новый объект Employee и его надо отобразить в View.

 

В этом случае в ViewModel создаем поле ObservableField<Employee>:

public class ViewModel {

   public ObservableField<Employee> employee = new ObservableField<>();

   ...
}

В это поле методом employee.set() репозиторий будет помещать новый Employee.

 

В layout в качестве переменной используем ViewModel. 

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

   <data>
       <variable
           name="model"
           type="ru.startandroid.application.data.ViewModel"/>
   </data>

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{model.employee.name}" />

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{model.employee.address}"/>
   </LinearLayout>

</layout>

Достаем из model объект employee и используем его поля в биндинге.

 

Теперь при обновлении значения в ObservableField<Employee> будут изменены и поля в View без каких-либо дополнительных действий с нашей стороны.

 

 

 

BaseObservable  

Есть еще один способ включить автобиндинг для Java объекта.

Делается это наследованием BaseObservable:

public class Employee extends BaseObservable {

   private long id;
   private String name = "";
   private int salary = 0;

   public Employee(long id, String name, int salary) {
       this.id = id;
       setName(name);
       setSalary(salary);
   }

   @Bindable
   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
       notifyPropertyChanged(BR.name);
   }

   @Bindable
   public int getSalary() {
       return salary;
   }

   public void setSalary(int salary) {
       this.salary = salary;
       notifyPropertyChanged(BR.salary);
   }
}

Поле id я оставил обычным. А поля name и salary будут отслеживаться биндингом. Для этого надо пометить get-методы аннотацией @Bindable, а в set-методах вызывать notifyPropertyChanged метод, который и будет уведомлять биндинг об изменениях значения поля.

В layout все будет как обычно. 

 

 

 

Двусторонний биндинг

Биндинг может работать в обе стороны. Т.е. он будет не только передавать данные в View, но и получать их оттуда.


Рассмотрим на примере пары полей в Employee:

public class Employee {

   public String name = "";
   public boolean enabled = true;

}

Поле name и статус enabled. Настроим биндинг этих полей в EditText и CheckBox.

При этом сделаем так, чтобы биндинг работал в обе стороны. Для этого надо в строке биндинга добавить символ = между @ и {...}

<EditText
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@={employee.name}"/>

<CheckBox
   android:text="enabled"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:checked="@={employee.enabled}"/>

Теперь при изменении текста в EditText, биндинг будет передавать новое значение в employee.name. А при включении\выключении чекбокса, биндинг будет передавать текущее состояние этого чекбокса в поле employee.enabled. 

Т.е. изменения содержимого View будут отражены в Employee объекте, который мы передавали в биндинг. Если необходимо, можно использовать и Observable поля. С ними это тоже будет работать.

 

Кстати, если после передачи в биндинг вы нигде не храните у себя объект employee, то вы всегда можете получить его обратно методом binding.getEmployee().

 


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

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

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

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




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal