分類
Uncategorized

work with observable data objects (使用可觀察數據對象) (Data Binding)

前言

可觀察性(Observability)是指一個物件具有當其數據發生變化時通知其他元件的能力。
Data Binding Library讓物件,欄位,集合具有可被觀察的能力
任何的 POJO 都可以使用於 Data Binding,但修改該物件時並不會讓 UI 也跟著更新。 Data Binding 可為數據對象(data object)提供在其數據更改時通知其他對象(稱為偵聽器)的能力。
可觀察類別(observable class)有 3 種類型,objects,fields,collections
當上列3種可觀察類別的物件綁定了 UI,且物件的屬性(property)發生變化時,UI 將會自動更新。
 

Observable fields

因為去建立實作 Observable 介面的類別需要一些額外工作,因此若開發者僅有少量的屬性其實是不值得的,在這種情況下可以使用一般性可觀察類別,如下

ObservableBoolean ObservableByte ObservableChar ObservableShort
ObservableInt ObservableLong ObservableFloat ObservableDouble
ObservableParcelable

 
Observable fields 是具有單一欄位的自包含可觀察物件,使用方式為建立一個 public final 屬性,如下

private static class User {
    public final ObservableField<String> firstName = new ObservableField<>();
    public final ObservableField<String> lastName = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}

要存取數值,使用 set 和 get 方法如下

user.firstName.set("Google");
int age = user.age.get();

注意: Android Studio 3.1 或更高的版本可讓開發者以 LiveData objects 代替 observable fields,可提供額外的好處。參考 Use LiveData to notify the UI about data changes
 

Observable collections

有些 App 使用動態資料結構來持有數據,observable collections 為提供這些動態資料結構。
ObservableArrayMap 用於當鍵值為參考型別時特別有用,如下

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在 layout 檔案中,map 使用如下

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
    android:text="@{user.lastName}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text="@{String.valueOf(1 + (Integer)user.age)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

ObservableArrayList 則適用於當鍵值為 integer 時,如下

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在 layout 檔案中,list 使用如下

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
    android:text='@{user[Fields.LAST_NAME]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

 

Observable objects

當類別實作了 Observable 介面便可提供註冊監聽器,該監聽器可讓想觀察的元件可取得通知。
Observable 介面具有加入和移除監聽器的機制,但開發者必須決定何時送出通知。為了讓過程更簡單,Data Binding Library 提供了 BaseObservable 類別,該類別實作了監聽器註冊機制。
當屬性發生變化時,實作 BaseObservable 的類別便負責通知。透過在 getter方法加入 Bindable 註釋,並在 setter 方法呼叫 notifyPropertyChanged 方法,如下

private static class User extends BaseObservable {
    private String firstName;
    private String lastName;
    @Bindable
    public String getFirstName() {
        return this.firstName;
    }
    @Bindable
    public String getLastName() {
        return this.lastName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

 
Data Binding 會在 module package 產生一個 BR 的類別,該類包含用於數據綁定的資源的 ID。
Bindable 註釋在編譯期間會產生一個 entity 到 BR 類別。
如果無法更改 data class 的 base class,則可以使用 PropertyChangeRegistry 實現 Observable 接口,以有效地註冊和通知監聽器。

分類
Uncategorized

Layouts and binding expressions (佈局和綁定表達式)(Data Binding)

1.簡介

表達式語言(expression language)允許開發者編寫處理視圖調度事件的表達式。
Data Binding Library 會自動生成視圖與數據對象綁定所需的類別(綁定類別)(binding class)。
數據綁定佈局文件(data binding layout files)和一般佈局文件不同。基本上會以 layout 標籤開頭,後面跟著 data 元素和 view 的元件。這個 view 元件代表非綁定部分的根節點。如下範例所示

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

在 data 元素的 user 變數代表可在這個佈局中使用的屬性。

<variable name="user" type="com.example.User" />

表達式透過 @{} 語法將變數和佈局結合,如下 TextView 將被設定給 user 變數的 firstname 屬性。

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

Note:
因為佈局表達式(layout expression)無法進行單元測試,因此應該保持小而簡單,開發者可以透過 binding adapter 保持簡潔。

2.Data Object(資料物件)

假設有個 User 類別如下

public class User {
 
  public final String firstName;
  public final String lastName;
 
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

這種類型的物件具有永不改變的數據,該數據通常會讀取一次且不會更改。也可以使用遵循一組約定的對象,例如 Java 中的訪問器方法,如以下

public class User {
  private final String firstName;
  private final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
  public String getFirstName() {
      return this.firstName;
  }
  public String getLastName() {
      return this.lastName;
  }
}

從數據綁定的角度來看,這兩個類別是相同的。
以下方表達式而言

<TextView
   …
   android:text="@{user.firstName}"
/>

等同於存取第一個類別的 public final String firstName 變數,也等同於呼叫第二個類別的 getFirstName 方法
以編程方式來呈現如下

TextView firstName = (TextView)findViewById(….)
firstName.setText(user.firstName);
or
firstName.setText(user.getFirstName());

3.Binding Data(綁定類別)

Data Binding 基本上會為每個佈局文件產生綁定類別(binding class)。
預設綁定類別的名稱會根據佈局文件的名稱建立,建立方式為將其轉換 Pascal 大小寫並添加 Binding 後綴。
舉例來說若佈局文件名稱為 activity_main.xml,則相對應的綁定類別為 ActivityMainBinding。
綁定類別將會持有對應佈局文件的所有綁定內容(如 user 變數)。並知道如何為綁定表達式指定數值。
建立綁定類別的推薦方式為在擴展佈局時建立。如下

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   
   ActivityMainBinding binding = DataBindingUtil.setContentView(this,    R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}

在執行 App 時,將在 UI 中顯示 Test。或可以使用 LayoutInflater 獲取視圖,如下:

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

若開發者在 Fragment,ListView 或 RecyclerView adapter 內使用數據綁定,則可以使用綁定類別或 DataBindingUtil 類別的 inflate 方法,如下:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

4.Expression Language(表達式語言)

一般特性

數學 字串連接 邏輯 二進制 一元
+ – / * % + && || & | ^ + – ! ~
移位 比較 測試型態 分組 文字
>> >>> << == > < >= <= Instanceof () character, String, numeric, null
轉型 方法呼叫 欄位存取 陣列存取 三元運算符
Cast Method calls Field access [] ? :

範例

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

目前不支援的運算符

This Super New Explicit generic invocation
(顯式通用調用)

空結合運算符(Null coalescing operator) ??

?? 表示為空結合運算符,若該運算符的左邊為 null 則選擇運算符的左邊,若該運算符的左邊不為 null 則選擇運算符的右邊。如下

android:text="@{user.displayName ?? user.lastName}"

等同於

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

屬性參考(Porperty Reference)

表達式可以參考類別的屬性,也可以使用於 fields, getters, ObservableField objects

android:text="@{user.lastName}"

避免空指標異常(avoiding null pointer exception)
綁定類別會自動檢查空指標異常,並提供預設值。
若表達式 @{user.name} 若 user 為 null 則提供預設值 null (字串)。
若表達式 @{user.age } 且 age 的型別為 int,若 user 為 null 則提供預設值0。

集合(Collection)

一般的集合,如 array, list, map 都可以透過 [] 運算符來操作。如下

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="Map<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

Note:
也可以透過 object.key 來參考 map 的 元素。
如 android:text=”@{map[key]}” 等同於 android:text=”@{map.key}”

字串

可以使用單引號表示屬性值,或是使用雙引號表示字串。

android:text='@{map["firstName"]}'

也可以使用雙引號表示屬性值,但在這種情況下就必須使用 ` 表示字串

android:text="@{map[`firstName`]}"

資源

可以使用以下表達式來存取資源

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式化字串和複數可以提供參數來計算

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

若複數需要多個參數,必須全部傳遞它們

Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"

以下為資源對應的表示方式

Type Normal reference Expression reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

5.Event handling(事件處理)

Data Binding 可以透過表達式處理從視圖發送的事件(如 onClick() 方法)。
事件屬性名稱由監聽器的方法名稱決定,但有一些例外。如 View.OnClickListener 有方法為 onClick(), 則該事件的屬性為 android:onClick
除了 android:onClick 以外還有一些點擊事件的特殊項目。這些特殊項目必須使用不同的屬性來避免衝突。如下

Class Listener setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomOut

可以使用以下機制來處理事件
1.方法引用(method references)
在表達式中可以引用符合偵聽器方法簽名的方法。
當表達式為方法引用時,Data Binding 會包裝方法引用(method reference)和所有者物件(owner object)在監聽器中並將監聽器設定到目標視圖。
若方法引用為 null 則 Data Binding 不會建立監聽器並設定 null
2.監聽器綁定
當事件發生時使用 lambda 表示式。
Data Binding 會建立監聽器並設定到目標視圖上。當處理事件時,監聽器將會執行 lambda 表示式。

方法引用(method reference)

事件可以直接綁定到方法,類似於 android:onClick 可以指定給 Activity 的方法。
與 View onClick 相比,主要優點是表達式在編譯時期處理,因此如果該方法不存在或其簽名不正確,會收到編譯錯誤。
方法引用和偵聽器綁定的主要區別在於偵聽器實現是在綁定數據時建立的,而不是在觸發事件時建立。
若想在事件發生時再執行表達式,選擇監聽器綁定較好。
使用普通綁定表達式(normal binding expression)將事件分配給其處理者,其值為要調用的方法名稱。如下類別

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

表達式可以綁定視圖的點擊監聽器(click listener)到 onClickFriend 方法,如下

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

Note:
表達式方法的簽名必須和監聽器對象方法的簽名完全相同。

監聽器綁定(listener binding)

監聽器綁定為當事件發生時才執行綁定的表達式。類似於方法引用,但可以運行任意數據綁定表達式。該特性適用於 Android Gradle 2.0 或以上的版本。
在方法引用中,方法的參數必須和事件監聽器的參數完全相同。
監聽器綁定則是方法的回傳值和監聽器的回傳值必須相同(除非是回傳 null)。
如下類別

public class Presenter {
    public void onSaveClick(Task task){}
}

可綁定點擊事件到 onSaveClick 方法,如下

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>
    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>

在表達式使用 callback 時,Data Binding 會自動建立需要的監聽器並註冊到事件。當視圖發送事件時,Data Binding 會執行相對的表達式。當表達式執行時可以確保空指針(null)和執行緒安全。
在上面的示例中,我們未定義傳遞給 onClick(View)的視圖參數。
監聽器綁定提供了 2 個選擇來指定監聽器的參數。開發者可以忽略所有的參數或命名所有的參數。
若命名了參數,則開發者可以在表達式中使用它。如上面的範例可以改為

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

若想使用表達式的參數,如下

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

可以使用 lambda 表達式提供超過 1 個的參數。

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

若監聽事件的回傳值不是 void,則表達式必須回傳同類型的型態,如下

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果表達式執行結果為 null,則數據綁定將返回該類型的默認值。例如,引用類型為 null,int 為 0,布林值為 false 等。
若在表達式使用三元運算符則可使用 void,如下

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

避免複雜的監聽器

監聽器表達式可以讓代碼非常容易了解。若表達式過於複雜也會使佈局難以閱讀和維護。
這些表達式應該像 UI 的可用數據傳遞給回調方法一樣簡單並實現業務邏輯在監聽器表達式呼叫的回調方法中。

6. Imports, variables, and includes

Imports 用來在 layout 中可以參考到類別,variable 用來描述在表達式使用的屬性, includes 可在整個應用中重複使用複雜的佈局。
 

Imports

可在 layout file 簡單的參考到類別,必須寫在 data 元素裡面。如下 import View 類別到 layout file 中。

<data>
    <import type="android.view.View"/>
</data>

Import View 類別之後便可在表達式中參考它。如下在表達式中使用 View 類別的  VISIBLE 和 GONE 常數。

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

 型別別名(Type aliases)

若類別名稱發生了衝突,當中的類別便可重命名為別名。如下將 com.example.real.estate 的 View 類別重新命名為 Vista。

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>

Import other classes

import 的型別可以在變數或表達式中用來當作型別參考,如下

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
</data>

注意: Android Studio 還未完全支援 import,因此自動完成的功能還無法使用在 IDE 中。
Import 的型別也可以用於轉型。如下將 connection 屬性轉型成 User

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

在表達式中也可以使用 import 型別來參考靜態變數或方法,如下 import MyStringUtils 並使用其 capitalize 方法。

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

注意 java.lang.* 會自動 import。
 

Variables

可以在 data 元素中使用多個 variable元素,每個 variable 元素代表可能會使用在表達式的屬性。如下宣告了 user, image, note

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note" type="String"/>
</data>

變數的型別會在編譯時期檢查,因此若變數實作了 Observable 或 observable collection 將會被反射到型別上。
若變數是基本類別或介面且未實作 Observable ,則該變數是無法被觀察(observable)
當不同佈局文件用於各種配置(橫向或縱向)時,這些變量會被組合。佈局文件之間不可存在互相衝突的變數名稱定義。
每個變數都有 setter 和 getter 方法定義在綁定類別中。變數將會使用預設數值,如整數為 0,布林為 false,參考型別則為 null。
有個特別變數為 context 會自動產生以便用於表達式,該變數的數值為根視圖(root view) getContext 方法的回傳值。

Includes

透過使用 App 的命名空間和屬性的名稱,變數可以從包含的佈局傳遞到佈局綁定中。如下從 name.xml 和 contact.xml 佈局中 include user 變數。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

注意 : 不支援從 merge 元素使用 include 動作。如下不支援該用法

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>
分類
Uncategorized

finaldb 和 Serializable 合併使用問題

問題描述:

若有個物件想透過 finalDB 儲存但又實作了 Serializable 介面。
當去存取該物件時,會出現”serialVersionUID has type long, got null” 相關問題。
發生問題的 class 如下

@Table(name = "GoodItem")
public class GoodItem implements Serializable{
    private static final long serialVersionUID = -6588468312284378785L;
    @Id
    private String id;
...

顯示的錯誤訊息如下

java.lang.IllegalArgumentException: field xxx.serialVersionUID has type long, got null
at java.lang.reflect.Field.set(Native Method)
at net.tsz.afinal.db.table.Property.setValue(Property.java:70)
at net.tsz.afinal.db.sqlite.CursorUtils.getEntity(CursorUtils.java:45)
at net.tsz.afinal.FinalDb.findAllBySql(FinalDb.java:574)
at net.tsz.afinal.FinalDb.findAll(FinalDb.java:528)
...

基本上就是 serialVersionUID 和 finalDB 發生衝突。
 

解決方法:

將 serialVersionUID 加上  @Transient 註解來解決,如下

@Table(name = "GoodItem")
public class GoodItem implements Serializable{
    @Transient
    private static final long serialVersionUID = -6588468312284378785L;
    @Id
    private String id;
...

 
 

分類
Uncategorized

Data Binding 前置準備

版本限制

Data Binding 函式庫提供靈活性和廣泛的兼容性,目前可運行在 Android 4.0( API Level 14)以及更高的版本,以及建議使用最新的 Android Gradle 插件,Data Binding 可以運作在 1.5.0 及更高的版本
 

相依性

要啟用 Data Binding 函式庫,必須在 app module 的 build.gradle 檔案加入以下內容

android {
    ...
    dataBinding {
        enabled = true
    }
}

 

Android Studio 支援特性

Android Studio 提供以下關於 Data Binding 的特性
1.語法高亮
2.標示語法錯誤
3.XML 代碼自動完成
 

新型資料綁定編譯器(data binding compiler)

Android Gradle 3.1.0-alpha06 版本包含新型資料綁定編譯器,該編譯器能更快速的產生綁定類別(binding class)。
使用舊型資料綁定編譯器可能會產生多個錯誤訊息並提示找不到綁定類別,而使用新型資料綁定編譯器可避免這些狀況。
透過在 gradle.properties 檔案設定以下內容以啟用新型資料綁定編譯器

android.databinding.enableV2=true

 
Note:
注意 Android Studio 3.1 還未完全支援新型資料綁定編譯器,而 Android Studio 3.2 預設已支援新型資料綁定編譯器

分類
Uncategorized

Data Binding (Android Jetpack)

什麼是 Data Binding

Data Binding 是一個函式庫,允許開發者使用聲明性格式(而不是以編程方式)將佈局 (layout) 中的 UI 元件綁定到應用程序中的數據來源。

使用編程方式綁定

佈局元件 (layout) 通常會定義在 Activity 內並呼叫相關的 UI 框架方法。
如下透過呼叫 findViewById 找到 TextView 並綁定到 viewModel 的 userName 屬性

TextView textView = findViewById(R.id.sample_text);
textView.setText(viewModel.getUserName());

使用 Data Binding 綁定

以下則是使用 Data Binding 直接在佈局檔案指定文字給 widget,這種方式可以取代上面範例的 Java code

<TextView
    android:text="@{viewmodel.userName}" />

透過在佈局檔案中綁定元件的方式可以移除許多需要在 Activity 呼叫的 UI 框架方法。讓  Activity 更簡潔並容易維護,也能改善效能,避免記憶體洩漏及 Null Pointer Exception。

Using the Data Binding Library

1.前置準備

2.佈局(layout)和綁定表達式(binding expressions)

3.使用可觀察資料物件(observable data objects)

4.產生綁定類別(binding classes)

5.綁定轉接器(binding adapters)

6.綁定佈局到架構元件(bind layout views to architecture components)

7.雙向綁定(Two-way data binding)

分類
Uncategorized

Lifecycle-Aware Components

什麼是 Lifecycle-aware Components?

Lifecycle-ware components(生命週期感知元件)包含在 android.arch.lifecycle 套件中,基本上就是當元件(Activity or Service)的生命週期發生變化時,生命週期元件即會自動收到通知並觸發相對應的動作。
 

為什麼需要生命週期感知元件?

在此之前若開發者需要根據元件的生命週期執行相對應的動作時都必須寫在元件的對應方法中。最常見的例子為旋轉螢幕時需要儲存資料,不是寫在 onPause 方法就是寫在 onSaveInstanceState 方法。
但這種寫法最直接的缺點就是增加 Activity 的大小,讓 Activity 變得越來越大。
生命週期感知元件可以根據 Activity 或 Fragment 的當前生命週期狀態自動呼叫其行為。
 

Lifecycle

Lifecycle 是一個類別,擁有關於另一個元件(Activity or Fragment)生命週期狀態的資訊。
Lifecycle 使用 2 個列舉來追蹤其相關連元件的生命週期狀態:
1.Event(事件)
Event 透過 Framework 和 Lifecycle class 發送,Event 會對應於 Activity 或 Fragment 的回調事件(callback events)。
2.State(狀態)
相關連元件的當前狀態。
以下為 Event 和 State 兩者互相轉換的關係圖。

LifecycleOwner

LifecycleOwner 為只有一個方法的介面,該方法必須回傳 Lifecycle。
在 Support Library 26.1.0 已將 Activity 和 Fragment 繼承 LifecycleOwner 並寫好回傳 Lifecycle 的實作部分,也就是說 Activity 和 Fragment 就是 LifecycleOwner。
要自動感知 Activity 的生命週期,首先呼叫 getLifecycle 方法取得 Lifecycle,接著再呼叫 Lifecycle 的 addObserver 方法讓 LifecycleObserver 訂閱 Lifecycle。
基本上就是透過 LifecycleOwner 提供 Lifecycle,接著讓 LifecycleObserver 訂閱
Lifecycle,如此 LifecycleObserver 就能感知 Lifecycle 的變化。
 

LifecycleObserver

LifecycleObserver 的用途就是用來跟蹤繼承 LifecycleOwner 的元件,透過在方法上加上註解的方式,可以自動取得目標元件的相關生命週期變化。
一個最簡單用來感知 Activity 的 LifecycleObserver 如下

import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.OnLifecycleEvent;
import android.content.Context;
import android.util.Log;
public class LifecycleMainActivityObserver implements LifecycleObserver {
  private static final String TAG = LifecycleMainActivityObserver.class.getSimpleName();
  @OnLifecycleEvent(Event.ON_START)
  public void lifecycleStart() {
    Log.d(TAG, "detect lifecycle on start");
  }
  @OnLifecycleEvent(Event.ON_CREATE)
  public void lifecycleCreate() {
    Log.d(TAG, "detect lifecycle on create");
  }
  @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
  public void lifecycleResume() {
    Log.d(TAG, "detect lifecycle on resume");
  }
  @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
  public void lifecyclePause() {
    Log.d(TAG, "detect lifecycle on pause");
  }
  @OnLifecycleEvent(Event.ON_STOP)
  public void lifecycleStop() {
    Log.d(TAG, "detect lifecycle on stop");
  }
  @OnLifecycleEvent(Event.ON_DESTROY)
  public void lifecycleDestroy() {
    Log.d(TAG, "detect lifecycle on destroy");
  }
  @OnLifecycleEvent(Event.ON_ANY)
  public void lifecycleChange() {
    Log.d(TAG, "detect lifecycle change");
  }
}

註解 OnLifecycleEvent(Event.ON_CREATE) 就表示當目標元件處於 create 狀態時,該方法便會被呼叫。
接著只要在目標元件的 onCreate 方法初始化 observer,並呼叫 addObserver() 即可

public class LifecycleMainActivity extends AppCompatActivity {
  private LifecycleMainActivityObserver mLifecycleMainActivityObserver;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.lifecycle_main_activity);
    mLifecycleMainActivityObserver = new LifecycleMainActivityObserver();
    getLifecycle().addObserver(mLifecycleMainActivityObserver);
  }
}

最後 LifecycleMainActivityObserver 的方法便會對應 LifecycleMainActivity 的生命週期改變自動被呼叫。
 

關於生命感知週期元件的最佳實踐

1.保持 UI Controllers(Activity or Fragment)盡量精簡,它們不該存取自己的資料,而應該交由 ViewModel 來執行,並透過 LiveData 將資料的變更反射回 View。
2.嘗試實作數據驅動的 UI,其中 UI Controller 負責在數據更改時更新視圖,或將用戶操作通知給 ViewModel。
3.應該將數據邏輯放到 ViewModel,把 ViewModel 當作 UI 和其他元件的連接器且 ViewModel 不應該去直接獲取數據(如本地端資料庫,遠端網路)。而是透過其它元件來取得數據,再返回給 UI controller。
4.使用 Data Binding 來建立 View 和 UI Controller 之間的簡潔介面,可讓 View 更具聲明性並大量的減少在 Activity 和 Fragment 需要寫的代碼。也可以考慮使用 Butter Knife 來避免樣版代碼以及建立良好的抽象。
5.若 UI 非常複雜可以考慮建立 Presenter 來處理 UI 的變化。
6.不要在 ViewModel 中引用 View 或 Activity context,否則可能造成記憶體洩漏。
 

生命感知週期的使用案例

1.動畫,當 App 位於後台時停止動畫,返回前台時恢復動畫
2.網路連接,當 App 位於後台時暫停網路,返回前台時恢復網路
3.視頻緩衝,當 App 啟動時盡快啟動緩衝,但直到App完全啟動時再播放視頻。可在App銷毀時終止視頻緩衝。
4.位置更新,當 App 位於後台時使用粗粒度位置更新,返回前台時使用細粒度位置更新。
 

分類
Uncategorized

Android Architecture Components

什麼是 Android Architecture Components

Android Architecture Components(AAC) 是函式庫的集合,可以幫助開發者寫出穩健,可測試,維護性高的 App。
 
AAC 包含了以下函式庫

Data Binding Lifecycles LiveData Navigation Paging
將可觀察資料綁定到 UI 元件 管理 Activity 和 Fragment 生命週期 當資料改變時通知視圖(View) 處理 App 導航功能 循序的從數據來源讀取資料
Room ViewModel WorkManager
流暢存取 SQLite 以生命週期方式管理 UI 相關資料 管理後台任務

 
也可以看到 AAC 涵蓋了 App 的整個架構主體。
基本上有以下 4 個要點

1. lifecycle-aware components 管理 Activity 或 Fragment 的生命週期。

可以避免記憶體洩漏(memory leaks),減少處理配置變更(configuration changes)的成本,更簡易的讀取資料到 UI。

2. LiveData 建立資料物件,當資料改變時會自動通知視圖(Views)。

可以套用 Observer Pattern 的觀念,把 LiveDate 當作 Subject,視圖當作 observer。
視圖會訂閱 LiveData,因此當 LiveData的資料發生變化時,視圖會自動收到通知。

3. ViewModel 保存 UI 相關的資料,讓 App 旋轉螢幕時不會銷毀資料。

ViewModel 就是擔任 MVVM Pattern 的 VM 角色,透過隔離視圖邏輯和數據層邏輯,方便撰寫測試,專注業務邏輯。

4. Room 能更快速的存取資料庫(SQLite)並可搭配 RxJava 協同使用。

Room 為 ORM 的一種,和其它流行的框架類似能夠大量簡化 SQL 語法。
 

分類
Uncategorized

Android Jetpack (噴射背包)

什麼是 Android Jetpack?

Android Jetpack 是一系列軟體元件的組合,透過這些元件可以幫助開發者寫出最佳實踐,避免樣板代碼並簡化複雜任務,讓開發者專注需要關心的代碼。
Jetpack 包含在 androidx.* 的套件名稱,沒有和任何平台 API 綁定,它向後相容並可頻繁更新,代表開發者可以隨時使用最新最好的版本。
 

使用 Jetpack 的好處

1.加速開發

Jetpack 元件可單獨或組合使用,同時利用 Kotlin 提供的特性來加速開發

2.消除樣板代碼

Jetpack 特別用來管理複雜的行為,如後台任務,導航,生命週期。讓開發者專注需要關心的部分

3.建立高品質,穩固的 App

Jetpack 可以減少崩潰,減少記憶體洩漏並提供向後兼容性
 

Android Jetpack Components(元件組)

Jetpack 主要有 4 個分類,各為 Fundation,Architecture,Behavior,UI,每個分類都包含不同數量的元件,各個元件提供特定功能並可單獨或協同使用。

1.Foundation

提供向後兼容,支援 Kotlin,測試相關功能

AppCompat Android KTX Multidex Test
為舊版 App 提供向後兼容性 支援 Kotlin 語言 支援多 DEX 檔案 Android 測試框架,提供單元測試和 UI測試

 

2.Architecture (Android Architecture Components)

提供建立穩固,可測試,可維護性高的 App

Data Binding Lifecycles LiveData Navigation Paging
將可觀察資料綁定到 UI 元件 管理 Activity 和 Fragment 生命週期 當資料改變時通知視圖(View) 處理 App 導航功能 循序的從數據來源讀取資料
Room ViewModel WorkManager
ORM的一種,可流暢存取 SQLite 以生命週期方式管理 UI 相關資料 管理後台任務

 

3.Behavior

讓 App 和標準 Android 服務集成,如 Notification,Permission,Sharing,Assistant

DownloadManager Media & playback Notifications Permissions Sharing Slices
排程和管理大量下載 播放媒體(包含Google Cast) 提供通知功能並可向後兼容 檢查和要求 App 權限 用於 Action bar 的共享操作 可在 App 之外顯示資料的 UI

 

4. UI

提供 Widgets 讓 App 使用簡單且有趣

Animation & transitions Auto Emoji Fragment Layout
用來移動或轉換 widget 幫助開發 Android Auto 元件 可在舊版本上使用最新的Emoji 功能 可元件化的基本單位 關於 App 版面配置
Palette TV Wear OS By Google
從調色板中提取有用訊息 幫助開發 Android TV 元件 幫助開發 Android Wear 元件

 
 
 

分類
Uncategorized

ViewModel 基本紀錄(Android Architecture Component)

Overview

ViewModel 為 Android Architecture Component 其中一部分,主要的用途為以自動管理生命週期方式來儲存與管理 UI 相關數據。好處為當配置更改(螢幕旋轉)之後能夠讓數據繼續存在
Android 管理 UI 控制器(Activity, Fragment)的生命週期,在管理的過程中可能會決定銷毀或重新創建 UI 控制器,以反應某些完全不受控制的使用者操作或設備事件。
如果系統銷毀或重新創建 UI 控制器,則存儲在其中的任何 UI 相關數據都將消失。
例如,App 可能會在其中一個 Activity 中包含使用者清單。當配置更改後重新創建 Activity 時,新的 Activity 必須重新取得使用者清單。
對於簡單的資料,Activity 可以使用 onSaveInstanceState() 方法並從 onCreate() 中恢復數據,但是這種方法僅適用於可以序列化然後反序列化的少量數據,而不適用於大量數據。
另一個問題是 UI 控制器經常需要進行花費一些時間才能返回的異步調用。
UI 控制器需要管理這些調用並確保系統在銷毀後清理它們以避免潛在的內存洩漏,這種管理動作需要大量維護動作,並且當配置更改重新創建物件的情況下會浪費資源,因為物件可能必須重新發出已經進行的調用
Activity 和 Fragment 之類的 UI 控制器主要用於顯示 UI 數據並反應使用者操作或處理系統通信,例如許可請求。
若是要求 UI 控制器也負責從資料庫或從網路加載數據,這會大量增加UI控制器的大小。
另外若 UI 控制器分配過多的責任可能導致單個類別嘗試自己處理應用程序的所有工作,而不是將工作委託給其他類別。以這種方式為 UI 控制器分配過多的責任也會使測試變得更加困難。
因此將視圖數據邏輯與 UI 控制器邏輯分離起來更容易,更有效。
Architecture Components 為 UI 控制器提供 ViewModel,其主要功能為為 UI 準備數據。ViewModel 物件在配置更改期間會自動保留,讓它們保存的數據可立即用於下一個 Activity 或 Fragment 實例。
例如,如果需要在應用程序中顯示使用者清單,請確保把取得使用者清單的工作放在  ViewModel,而不是 Activity 或 Fragment,如下

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }
    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

接著你可以使用下方的方式取得使用者清單

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

如果重新建立 Activity,新的 Activity 將接收由前一個 Activity 的相同 MyViewModel 實體。當 Activity 結束時,將呼叫 ViewModel 的 onCleared() 方法,以便清理資源。
注意:在 ViewModel 中絕對不可參考到視圖(View),LifeCycle,或任何一個類別其可能持有 Activity context。
ViewModel 的設計原則之一就是讓測試容易撰寫,可以在不知道 View 或 LifeCycle 寫測試。
ViewModel 可以包含 LifecycleObservers 物件,如 LiveData。但 ViewModel 永遠不會觀察到生命週期感知的可觀察對象(如 LiveData 物件)的變化。
以架構來說通常是作為 MVVM 的 VM(ViewModel) 角色來呈現, 透過把和 View 不相關的邏輯提取出來放到 VM,一方面可以減輕 View 的職責,一方面提高其它模組內聚力,也讓測試更容易撰寫。

Dependency

參考官網

實作 ViewModel

實作方式非常簡單,只要繼承 ViewModel 即可。通常 ViewModel 會和 LiveData 同時使用關於 LiveDate 請參考這裡
如同 Overview 提到的 ViewModel 可以在配置改變時自動保留數據,因此應該盡量把數據部分放到 ViewModel 而不是 Actvity 或 Fragment

public class MyViewModel extends ViewModel {
  private static final String TAG = MyViewModel.class.getSimpleName();
}

Note:通常會和 LiveData一起使用,但這裡為了方便說明,先不加入 LiveData。

Using in Activity

public class ViewModelMainActivity extends AppCompatActivity {
  private static final String TAG = ViewModelMainActivity.class.getSimpleName();
  private MyViewModel mViewModel;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.view_model_main_activity);
    mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    Log.d(TAG,"mViewModel.toString():"+ mViewModel.toString());
  }
}

第12行透過 ViewModelProviders.of(this).get(MyViewModel.class); 初始化 mViewModel。
第13行用來測試旋轉螢幕後是否為同一個 ViewModel 實體。接著你可以開始旋轉螢幕了。
Note :
1.注意在 ViewModel 內絕不可引用 View, Lifecycle, 或是任何本身還會引用 activity context 的類別
若 ViewModel 內需要使用 context 就繼承 AndroidViewModel
2.當呼叫 ViewModelProviders.of(this).get(MyViewModel.class); 之後,ViewModel 就會保存在記憶體中,直到它的作用域消失。也就是當 ViewModelMainActivity 為 finish 狀態時,ViewModel 即會消失。

LiveDate 和 ViewModel 共用

LiveData 的用途為提供 observer 可以觀察本身所擁有的數據,而 ViewModel 則是提供 View 數據並可自動處理配置變化所引發的事件。
這兩者共同使用對於 View 來說便可發揮極大的功效。

public class MyViewModel extends ViewModel {
  private static final String TAG = MyViewModel.class.getSimpleName();
  private MutableLiveData<List<User>> mUsers;
  public LiveData<List<User>> getUsers() {
    if (mUsers == null) {
      mUsers = new MutableLiveData<>();
      loadUsers();
    }
    return mUsers;
  }
  private void loadUsers() {
   ...
  }
}

 

Scope of ViewModel

從圖中可以看到當 Activity 在 onCreate 時,ViewModel 就跟著產生。而當 Activity finish 之後,呼叫完 onDestroy 方法後,ViewModel 就跟著消失。

在 Fragment 之間分享數據

若同一個 Activity 擁有 2 個 Fragment , 也可以使用 ViewModel 讓這 2 個 Fragment 共享數據。

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
    public void select(Item item) {
        selected.setValue(item);
    }
    public LiveData<Item> getSelected() {
        return selected;
    }
}
public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}
public class DetailFragment extends Fragment {
 public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

注意這兩個 Fragment 都使用了 getActivity 來取得 ViewModelProvider,因此,這兩個 Fragment 都接受相同的 SharedViewModel 實體。
這個做法有以下優點
1. Activity 不需要做任何事,也不需要了解 Fragment 之間的溝通。
2. Fragment 不需要彼此了解。若其中一個 Fragment 發生問題,另一個 Fragment 繼續正常工作。
 
 
 
 
 
 
 

分類
Uncategorized

LiveData 基本紀錄(Android Architecture Component)

LiveData 為 Android Architecture Component 其中之一,主要用途為持有可觀察數據(observable data),並讓有註冊且為活躍狀態的觀察者自動收到更新。
若了解或實作過 Observer Pattern (觀察者模式)會很容易了解,可以把 LiveData 看作 Subject (主題)也就是資料的來源。對 Subject 有興趣的 observers 可以訂閱主題,訂閱完成後當主題內的數據發生變化時,observers 便會自動收到通知。
基本上 Observer Pattern 是用來取代 polling 的解決方案。關於 Observer Pattern 可以參考這篇
 
回過頭來,首先看看 LiveData 的 OverView (這裡是官網介紹)
首先 LiveData 是一種擁有可觀察數據的類別(看字面不好懂,直接看實作吧) e.g.,

  private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();

因為 LiveData 為 abstract class,通常在實作上會以 MutableLiveData (LiveData’s subclass)取代 LiveData。角括號可以傳入任何型別,包含 Collections,list 等等。
LiveData 最大的特色為它具有生命週期感知(Lifecycle-aware)能力該能力可以讓 LiveData 得知其他元件(Activity, Fragment, Service)的生命週期。
因此可以避免很多必須手動操作的情況,如螢幕旋轉時需要儲存數據,UI 和數據之間的同步,memoey leak 等等。
通常 LiveData 會和 ViewModel 同時使用。

如何使用 LiveData

1.建立 LiveData 實體並持有特定類型的數據。這通常在 ViewModel 類別內實作
2.建立 Observer 物件並定義 onChanged 方法,該方法內容用來描述當 LiveData 持有的數據改變時所作的事。你通常會在 UI Controlller 內(Activity, fragment)建立 Observer 物件
3.使用 observe 方法連結 Observer 物件和 LiveData 物件。observer 方法需要傳入LifecycleOwner 物件,這讓 Observer 物件訂閱 LiveData 物件並讓 LiveData 可以通知改變。你通常會在 UI Controller 內(activity, fragment)連結 Observer 物件
Note:
你可以藉由 observeForever(Observer) 方法來註冊一個 observer 而不需要結合 LifecycleOwner。在這種情況下 observer 會被當作永遠都是活躍的,因此總是會收到改變的資訊。反之,可以使用 removeObserver(Observer) 來移除 observer
當 LiveData 的資料被更新之後,所有的已註冊且相關連的 LifecycleOwner 為活躍的 observers 會收到通知。

Example

1.Dependency
(參考官網)

2.Create LiveData objects

LiveData 可以包含任何類型的數據,如 List,Map 等等。
LiveData 通常儲存在 ViewModel 內並透過 getter 方法來存取。

public class NameViewModel extends ViewModel {
  private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();
  public MutableLiveData<String> getCurrentName() {
    return mCurrentName;
  }
}

Note:確保 LiveData 儲存在 ViewModel 而不是 activity 或 fragment

3.Observe LiveData objects

在大多數的情況下,onCreate 方法為初始觀察 LiveData 位置
在一般的情況下 LiveData 只在數據有更新時且觀察者為活躍的狀態才發送通知。但有 2 個例外,第 1 為當觀察者從非活躍狀態轉為活躍狀態時會收到通知
第 2 為當觀察者是第 2 次從非活躍狀態轉為活躍狀態,則只有在自上次活動狀態以來該值發生變動時才會收到更新
(也就是說當觀察者第 1 次從非活躍轉成活躍一定會收到更新。若是第 2 次從非活躍轉成活躍,且必須儲存的數據發生變化才會收到更新)

public class LiveDataMainActivity extends AppCompatActivity {
  private NameViewModel mViewModel;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.live_data_main_activity);
    mViewModel = ViewModelProviders.of(this).get(NameViewModel.class);
    Observer<String> nameObserver = new Observer<String>() {
      @Override
      public void onChanged(String newName) {
        //call back when mViewModel's mCurrentName changed
      }
    };
    mViewModel.getCurrentName().observe(this, nameObserver);
  }
}

 

4.Update LiveData objects

LiveData 可以透過 setValue 和 postValue 方法來改變數據,2者的差別為 setValue 可用於main Thread,postValue 可用於 worker thread

String newName = "john doe";
mViewModel.getCurrentName().setValue(newName);

因此當 setValue 呼叫之後,所有已註冊且為活躍的 observer 便會觸發 onChanged 方法

完整 source code

NameViewModel.java

import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
public class NameViewModel extends ViewModel {
  private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();
  public MutableLiveData<String> getCurrentName() {
    return mCurrentName;
  }
}

LiveDataMainActivity.java

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class LiveDataMainActivity extends AppCompatActivity {
  private static final String TAG = LiveDataMainActivity.class.getSimpleName();
  private NameViewModel mViewModel;
  private TextView mName;
  private Button mChangeName;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.live_data_main_activity);
    mName = (TextView) findViewById(R.id.name);
    mChangeName = (Button) findViewById(R.id.change);
    mChangeName.setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        String newName = "john doe";
        mViewModel.getCurrentName().setValue(newName);
      }
    });
    mViewModel = ViewModelProviders.of(this).get(NameViewModel.class);
    Observer<String> nameObserver = new Observer<String>() {
      @Override
      public void onChanged(String newName) {
        mName.setText(newName);
      }
    };
    mViewModel.getCurrentName().observe(this, nameObserver);
  }
}

live_data_main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  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"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".LiveDataMainActivity">
  <TextView
    android:id="@+id/name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>
  <Button
    android:id="@+id/change"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/name"
    android:text="Change name"
    />
</RelativeLayout>