30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ViewModel考察

Posted at

本記事を書く経緯

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() {
    ...
}

上記のViewModelProviderViewModelStoreOwnerViewModelProvider.FactoryViewModelの取得に関わる主要人物です。一個ずつ見ていきます。

ViewModelStoreOwner

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

ViewModelStoreOwnerViewModelStoreを返す役割を持っています。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.FactoryViewModel生成に関わっています。
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の役割です。FactoryViewModelStoreOwner制御してViewModelを返します。

Factoryを使ってViewModelを生成し、ViewModelStoreに入れる作業をするし、ViewModelがすでにViewModelStoreに入っていたら生成しないで、直接ViewModelを返します。

ActivityのViewModelStoreOwnerの実装

回転などで再生成されたActivityでも一律前と同じViewModelStoreのインスタンスを返すことで、Activityのライフサイクルを超えてViewModelは生存できました。

ではActivityはどうやって同じViewModelStoreを返すのか、これはComponentActivity1内のgetViewModelStoreの実装を見ればわかります。
結論を先にいうと、getLastNonConfigurationInstanceonRetainNonConfigurationInstanceを使って実現してました。

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される前にViewModelStoreonRetainNonConfigurationInstanceで一旦退避させて、Activityが再生成されてgetViewModelStoreが呼ばれたときに同じオブジェクトを返すようにしました。

このgetLastNonConfigurationInstanceonRetainNonConfigurationInstanceは、今では使われない機能らしいですが、昔はそれなりに活躍していたらしいです。だが今はComponentActivity内ではonRetainNonConfigurationInstancefinalにしているので、オーバーライドさせてもらえないです。
(代わりに名前が似ているド同等機能のonRetainCustomNonConfigurationInstancegetLastCustomNonConfigurationInstanceがありますが、この2つはfinalになってないのでオーバーライドできますが、Deprecatedな関数なので、あえて使う必要がないでしょう。)

結論

ViewModel生成Factoryの責務で、ViewModelの保存・生存期間ViewModelStoreOwner(ViewModelStore)の責務です。

Activity間でViewModelを共有したければ、共通のViewModelStoreを持てばいいです。
そのViewModelStoreはせめてActivityより長生きしないとだめなので、Activityより長いライフサイクルを持つところにそのViewModelStoreを置くしかないです。

つまり手取り早くApplicationに置くことになりそうです。(もしくはDaggerでViewModelStoreのシングルトンを注入します)

  1. ComponentActivity https://android.googlesource.com/platform/frameworks/support/+/8514bc0f4d930b5470435aa365719b2a6a3ad2f3/activity/src/main/java/androidx/activity/ComponentActivity.java#217

30
12
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
30
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?