Android Data Binding. Observable поля. Двосторонній біндинг.
Коли ми використовуємо біндинг для звичайного 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().