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 繼續正常工作。