注意
ViewModelの意訳です。
わかりづらい表現を意訳したり、回りくどいところを端折ったりしています。
和訳に自信がないところもあるため、間違いを見つけた場合は指摘してください。
・・・・
Activity、FragmentのライフサイクルはAndroidフレームワークで管理されています。
このフレームワークがユーザ操作、デバイスのイベントによってActivity、Fragmentを破棄、生成するかを勝手に決定します。
これによって、私たち開発者は様々なことを考慮をしなくてはなりません。
以下の問題点にアプローチするためにViewModel
は作られました。
問題点
・大きいデータを保持するのが困難
小さいデータならonSaveInstanceState()
で保存すればいいけど、大きなデータだとそうはいかない。
だから画面が再生成されるたびにAPIに通信してデータをとってくる。
・onDestroy()
でリソースの解放を行う必要がある
解放するのを忘れるとメモリリークするし、メンテナンスが大変
###・Activity、Fragmentに機能の持たせ過ぎ
APIとの通信処理、ユーザアクション、DBへの保存...などの機能をActivity、Fragmentに持たせがち。
おかげでテストがすごく大変。
ViewModelでデータを管理する
ViewModelはActivity、Fragmentの画面構成の変更(画面回転など)などによって再生成される際にデータを自動的に保持します。
これによって再生成されたActivity、FragmentでもすぐにViewModelかたデータを取り出し、使用することができます。
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// do async operation to fetch users
}
}
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
ActivityのonCreate()
でViewModelを取得しています。
Activityが再生成された場合でもViewModelは以前のActivityで使用していた同じViewModelです。
ViewModelはActivityが破棄されるまで生存し続けます。(ViewModelのライフサイクルについてはこちら)
ViewModelを保持するActivityが破棄された場合、フレームワークがViewModelのonCleared()を呼び出して、ViewModelで保持していたリソースの解放を行います。
注意
ViewModelはActivity、Fragmentよりも生存期間が長いので、ViewやActivityContextなどを参照しない様にしてください。
もしApplication contextが必要な場合はAndroidViewModelを継承してください。
AndroidViewModelはApplication
をコンストラクタの引数に持ちます。
Fragment間のデータ共有
一覧と詳細画面がFragmentで構成されている画面を想像してください。(Gmailのタブレット版のような)
Activityは各Fragmentに対してインターフェースによってデータを渡したり、Fragment間の橋渡しを行います。
また、各Fragmentのライフサイクルも気にする必要があり、これもActivityの肥大化とコードをより複雑にしています。
2つのFragmentは共通のViewModelにアクセスすることでこれらの問題を解消することができます。
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 onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onActivityCreated() {
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
ViewModelの取得方法に注目してください。
ViewModelProvidersにgetActivity()
を渡しています。
これにより、各Fragmentで共通のViewModelを取得することができます。
共通のViewModelを使用することで、こんな問題が解消されます。
・ActivityはFragmentのことについて何もしらなくて良い
今までは、一覧Fragmentのアイテム選択をコールバックによってActivityに通知して、Activityから詳細Fragmentにコンテンツを表示するように通知していたと思います。
これに対してViewModelを使用した場合は、詳細FragmentはViewModelが持っているselected
だけを気にすれば良いだけになります。
一覧Fragmentでアイテムが選択された場合はselected
に選択されたアイテム情報を代入し、詳細Fragmentはselected
が変更されたことを受けてコンテンツの変更を行う事ができます。
・各Fragmentは独自のライフサイクルをもっており、お互いのライフサイクルを気にする必要はありません
一覧Fragmentが先に生成されて、詳細Fragmentの生成がまだの場合はそれをまってからコンテンツの表示を行う必要がありました。
ViewModelを使用することによって、詳細Fragmentは表示可能なタイミングでViewModelから表示するコンテンツのデータを取り出して表示すれば良くなります。
ViewModelのライフサイクル
ViewModelオブジェクトは、ViewModelの取得時にViewModelProviderに渡されるLifecycle(Activity、Fragment)にスコープされます。
ViewModelは、Lifecycleのオブジェクトがメモリ上から消えるまで存在します。
Activityの場合は終了(破棄)するまで、Fragmentの場合はデタッチされるまで。