LoginSignup
20
15

More than 5 years have passed since last update.

【Android Architecture Components】ViewModel vs SavedInstanceState

Last updated at Posted at 2017-08-25

ViewModelのドキュメントにViewModel vs SavedInstanceStateと言う項目が増えてました。

結論から言うと、

ViewModelonSaveInstanceStateの代替えではない

と言う事です。

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);
}

参考

20
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
15