retainInstance = true
なFragmentにキャッシュされているので、Activity/Fragmentが本当に殺されるまで生き残ることができる。
Google I/O 2017で、Architecture Components
という新しいライブラリ群が発表されました。
LiveData
, ViewModel
, LifecycleObserver
, LifecycleOwner
, Room
といったこれらのライブラリは、開発者がより強固で、テスタブルで、かつメンテナンス性が高いアプリケーションを作るための手助けとなるべく作られています。
今回はその中でもViewModel
について少し調べてみました。
詳しい説明はViewModel
のリファレンスに譲りますが、簡単に言うと「Activity/Fragmentのローテーション等による再生成をこえて状態を保持するためのコンポーネント」です。
今までActivity/Fragmentのメンバ変数に保存していたような値や非同期処理をViewModel
に書いておけば、Activity/Fragmentが再生成されたとしても値や非同期処理の状態が維持されてそのまま使えます。
onSavedInstanceState
でいちいちBundle
に詰め込んだりしなくてすみます。 私自身はあまり使ったことがありませんが、AsyncTask
等をいちいち書く必要がなくなります。
でも、どうやって?
※これはGoogle I/O 2017で発表された1.0.0-alpha1時点での話です
ViewModel
は下記のような感じで取得します。使う側のActivity/Fragmentではキャッシュから取得するとか新規作成するとか、そういうことを意識する必要はありません。
FooViewModel viewModel = ViewModelProviders.of(fragment).get(FooViewModel.class);
ViewModelProviders#of
にはActivityかFragmentを渡すことができます。一つ注意することがあって、Fragmentを渡すときはfragment.getActivity() != null
でなければならず、detachされているFragmentでは使うことができません。
ViewModelProviders#of
はActivity/Fragmentのみを引数に取るものと、Activity/Fragmentに加えてViewModel
のファクトリクラスを引数に取るものがあります。
ファクトリクラスを渡さない場合は引数なしのコンストラクタを呼び出してViewModel
をインスタンス化するようです。
また、ViewModel
の代わりにAndroidViewModel
というinterfaceを実装すると引数がApplication
のコンストラクタを使う模様です。
Dagger等のDIライブラリを使う場合はファクトリクラスを使うことになりそうです。
さて、ViewModelProvider
はこんな感じでインスタンス化されています。
// ViewModelProviders.java
public static ViewModelProvider of(@NonNull Fragment fragment) {
FragmentActivity activity = fragment.getActivity();
if (activity != null) {
throw new IllegalArgumentException("Can't create ViewModelProvider for detached fragment");
} else {
initializeFactoryIfNeeded(activity.getApplication());
return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
}
}
呼び出されているコンストラクタのシグネチャはこんな具合です。
ViewModelProvider(ViewModelStore store, ViewModelProvider.Factory factory)
ViewModelStore
とかいかにもあやしい名前のクラスが引数にあるので詳しく見てみましょう。 ViewModelStores#of
の中を見てみるとこんな感じ
public static ViewModelStore of(Fragment fragment) {
return HolderFragment.holderFragmentFor(fragment).getViewModelStore();
}
HolderFragment
なるものが出てきました。
コードを見てみると、コンストラクタでsetRetainInstance(true);
を呼んでいます。retainInstance = true
にすると親Activity/Fragmentが再生成されても対象のFragmentは生き残るようになるので、この仕組みを使ってViewModel
をActivity/fragmentの再生成後も使えるようにしているようです。
ViewModelStore
はこのHolderFragment
で管理されています。
ViewModelStore
の実態はHashMap<String, ViewModel>
です。ここで基本的にはViewModel
のクラス名をキーとしてViewModel
のインスタンスを管理しています。
HolderFragment
はActivity/Fragment毎に作られるので、一つのViewModelStore
が管理するのは自HolderFragment
の直接の親Activity/FragmentのViewModel
だけです。
クラス名がキーなので同一Activity内、同一Fragment内で同じViewModel
を複数使うことはできなそうな感じもしましたが、よくよく見ると外部からキーを指定できるget
メソッドのオーバーロードも用意されていたので一応そういうユースケースも考慮されていそうです。
ViewModel
の保存周り、ホントはもうちょっとゴニョゴニョしてるんだけど概要としてはこんな感じです。とてもわかりやすいコードなのでぜひ一読してみてください。
ここ一年くらい触ってるyet another FragmentのConductorがController(Fragmentのようなもの)をキャッシュするのにやっぱりretainInstance = true
なFragmentを使ってて、Architecture Componentsの発表を聞いたときに同じようなことやってるのかなーって思ってたら案の定でちょっとニンマリしてしまった。
特に黒魔術してるわけでもなく、既存の仕組みをうまく使ってるだけなのでこの仕組み自体が黒歴史になることはなさそう。
ViewModel
はActivity/Fragmentへの参照を持つことは推奨されてないので必然的にユニットテストしやすいコードになっていきそう。
とはいえViewModel
だけで完全にテスタブルになるわけではないし、ViewModel
はActivity/Fragmentの再生成をこえた状態のキャッシュに一つの道を示しただけなので、これですべてが解決するわけではない。
今までの知見と組み合わせつつ、幸せになれるコードを書けるようやっていきましょう、ということでざっくりとしたコードリーディングを終わります。