本記事を書く経緯
ViewModelをよくこんな感じに使っていましたが
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
これはある程度の「魔術」だと思って、viewModels
の中で何をやっているのか、なんでActivityのライフサイクルを超えて生存できるのかを気にしていませんでしたが、Activity間で同じViewModelを共有したい要件に出会ったので、本格的にViewModel周りを調査してみました。
ViewModelの生成
まずAndroid KTXを使わないでViewModelを利用する原始的な書き方を見てみます。
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
// (Deprecated) viewModel = ViewModelProviders.of(this).get(MainViewModel::class)
viewModel = ViewModelProvider(this).get(MainViewModel::class)
}
}
Android KTXが出る前まではたぶん一番こういう書き方がよく見られます。
ただ、この書き方はデフォルトで用意されているViewModelProvider.Factory
を使用する前提になっているし、登場人物はまだ出揃っていないので、解説には不向きです。もうちょっとカスタマイズした書き方を見てみます。
// 2. Activityは元からViewModelStoreOwnerを実装しています
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
// 1. ViewModelProvider(owner: ViewModelStoreOwner, factory: ViewModelProvider.Factory)
val provider = ViewModelProvider(this, MainViewModelFactory())
viewModel = provider.get(MainViewModel::class)
}
}
// 3. 実際にViewModelを生成するFactoryクラス
class MainViewModelFactory : ViewModelProvider.Factory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
// ViewModelを手動生成する
return MainViewModel() as T
}
}
class MainViewModel : ViewModel() {
...
}
上記のViewModelProvider、ViewModelStoreOwner、ViewModelProvider.FactoryはViewModel
の取得に関わる主要人物です。一個ずつ見ていきます。
ViewModelStoreOwner
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
ViewModelStoreOwner
はViewModelStore
を返す役割を持っています。ViewModel
の保存に関わっています。
ViewModelStore
は自体一種のHashMapで、ViewModel
を保存/返す役割を持っています。
ViewModelStoreOwner
が返すViewModelStore
は毎回違うのか、それとも同じViewModelStore
を返すかはViewModelStoreOwner
の実装次第です。
ActivityはこのViewModelStoreOwner
を実装しています。回転などで再生成されたActivityでも一律前と同じViewModelStore
のインスタンスを返すように実装されているので、Activityのライフサイクルを超えてViewModel
は生存できました。
ViewModelProvider.Factory
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
ViewModelProvider.Factory
はViewModel
の生成に関わっています。
Factory
を使わなくても例えば普通にMainViewModel()
を呼んだら生成できますが、Factory
を通すことで 生成する/しない ・ 生成のタイミング をViewModelProvider
に移譲します。
ViewModelProvider
public class ViewModelProvider {
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
this(owner.getViewModelStore(), factory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
mFactory = factory;
mViewModelStore = store;
}
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
// mViewModelStoreに取得しようとしているViewModelがあればそのまま返す
// なければmFactoryを使ってViewModelを生成した上で、mViewModelStoreに入れてあとに返す
}
}
ViewModelProvider
は大体デザインパターンでいうとBuilderパターンのDirectorの役割です。Factory
とViewModelStoreOwner
を制御してViewModel
を返します。
Factory
を使ってViewModel
を生成し、ViewModelStore
に入れる作業をするし、ViewModel
がすでにViewModelStore
に入っていたら生成しないで、直接ViewModel
を返します。
ActivityのViewModelStoreOwner
の実装
回転などで再生成されたActivityでも一律前と同じViewModelStore
のインスタンスを返すことで、Activityのライフサイクルを超えてViewModel
は生存できました。
ではActivityはどうやって同じViewModelStore
を返すのか、これはComponentActivity1内のgetViewModelStore
の実装を見ればわかります。
結論を先にいうと、getLastNonConfigurationInstance
とonRetainNonConfigurationInstance
を使って実現してました。
class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner, ... {
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
}
mViewModelStoreがまだ初期化されていなければ、getLastNonConfigurationInstance
からViewModelStore
を取得します。
このgetLastNonConfigurationInstance
はあまり馴染みがないので、調べてみました。どうもViewModelより以前からある仕組みです。
public Object getLastNonConfigurationInstance()
public Object onRetainNonConfigurationInstance()
Activityがdestroyされる前にonRetainNonConfigurationInstance
は呼ばれます。この関数をオーバーライドして任意のオブジェクトを返せば、次にActivityが再生成されたときにgetLastNonConfigurationInstance
から完全同一オブジェクトを取得できます(そのオブジェクトは死なないです)。
ComponentActivityはこの仕組を利用して、Activityがdestroyされる前にViewModelStore
をonRetainNonConfigurationInstance
で一旦退避させて、Activityが再生成されてgetViewModelStore
が呼ばれたときに同じオブジェクトを返すようにしました。
このgetLastNonConfigurationInstance
とonRetainNonConfigurationInstance
は、今では使われない機能らしいですが、昔はそれなりに活躍していたらしいです。だが今はComponentActivity内ではonRetainNonConfigurationInstance
をfinal
にしているので、オーバーライドさせてもらえないです。
(代わりに名前が似ているド同等機能のonRetainCustomNonConfigurationInstance
とgetLastCustomNonConfigurationInstance
がありますが、この2つはfinal
になってないのでオーバーライドできますが、Deprecatedな関数なので、あえて使う必要がないでしょう。)
結論
ViewModel
の生成はFactory
の責務で、ViewModelの保存・生存期間はViewModelStoreOwner
(ViewModelStore
)の責務です。
Activity間でViewModel
を共有したければ、共通のViewModelStore
を持てばいいです。
そのViewModelStore
はせめてActivityより長生きしないとだめなので、Activityより長いライフサイクルを持つところにそのViewModelStore
を置くしかないです。
つまり手取り早くApplication
に置くことになりそうです。(もしくはDaggerでViewModelStore
のシングルトンを注入します)