ViewModelのドキュメントにViewModel vs SavedInstanceStateと言う項目が増えてました。
結論から言うと、
ViewModelはonSaveInstanceState
の代替えではない
と言う事です。
ViewModelを使ったとしても、引き続きonSaveInstanceState()
でデータを保存する必要があります。
ViewModel vs SavedInstanceState
要約するとこんな感じです。
ViewModelは設定の変更によって画面が再生成される場合はデータを保持しますが、メモリ不足などでAndroid OSからKillされた場合はViewModelも一緒に破棄されます。
つまり、ViewModelに保存していたデータも消えてしまいます。
一方で、onSaveInstanceState()
でBundleに保存したデータは、システムのプロセスメモリに保存されるので、Android OSによってアプリがKillされた場合でもデータは保持され続けます。
ただし、Bundleに保存できるデータには上限があるので、最小限のデータのみ保存してください。
例えば、国情報を表示する画面がある場合は、ViewModelに画面に表示する国情報を保存し、onSaveInstanceState
のBundleには国のIDのみを保存するようにしましょう。
画面が生成されるパターンとフロー
画面が生成されるパターンは3つあり、以下のようなフローで実装します。
最初にActivityが生成された時
この場合、onSaveInstanceState
のBundleデータなし、ViewModelも空の状態となります。。
ViewModelを生成してからの空のクエリをViewModelに渡して、ViewModelはクエリが空なのでデータのロードはしません。
Activityは初期化状態で始まります。
Android OSによってKillされた後に、再度アプリを表示した時
ActivityはonSaveInstanceState
でBundleに保存したクエリをもっていますが、ViewModelに保存したデータは消えています。
ActivityはそのBundleからクエリを取り出し、ViewModelに渡します。
ViewModelはデータが空なので、クエリを元にデータをロードするようにします。
設定の変更によってActivityが再生成された時
ActivityはonSaveInstanceState
でBundleに保存したクエリとViewModelの保存した検索結果をもっています。
onSaveInstanceState
でBundleに保存したクエリをViewModelに渡して、ViewModelはデータを取得し直す必要かどうかを検討します。
どうやって解決するか?
Bundle
に対応したViewModelProvider.Factory
を作成します。
class MyModelFactory implements ViewModelProvider.Factory {
private final Bundle bundle;
public MyModelFactory(Bundle bundle) {
this.bundle = bundle;
}
@Override
public MyViewModel create(Class modelClass) {
MyViewModel viewModel = new MyViewModel();
viewModel.readFrom(bundle);
return viewModel;
}
...
このViewModelProvider.Factory
はViewModelの生成と最後に渡したBundle
を保持する機能があります。
Bundle
にデータを書き込むには従来通り、Activity/FragmentでonSaveInstance
をオーバーライドします。
@Override
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
viewModel.writeTo(bundle);
}
汎用的に使えるように、MyModelFactory
のViewModelをジェネリクスで定義します。
class BundleAwareViewModelFactory<T extends ParcelableViewModel> implements ViewModelProvider.Factory {
private final Bundle bundle;
private final ViewModelProvider.Factory provider;
public BundleAwareViewModelFactory(@Nullable Bundle bundle,
ViewModelProvider.Factory provider) {
this.bundle = bundle;
this.provider = provider;
}
@SuppressWarnings("unchecked")
@Override
public T create(final Class modelClass) {
T viewModel = (T) provider.create(modelClass);
if (bundle != null) {
viewModel.readFrom(bundle);
}
return viewModel;
}
}
すべてのViewModelは下記のViewModelを継承するようにします。
public abstract class ParcelableViewModel extends ViewModel {
public abstract void writeTo(@NonNull Bundle bundle);
public abstract void readFrom(@NonNull Bundle bundle);
}