ViewModel、ViewModelProviderについて調べてみた(Android)

Keisuke Kawajiri
URL Memo
Published in
6 min readDec 27, 2019

先日、URLメモの動作確認をしていたのですが、画面回転後、メモの再検索が実行され、スクロールが戻る問題に気付きました。

修正前の動作

期待していた動作は、

  • 画面回転後、画面回転前のメモデータが表示されていること。
  • 画面回転後、画面スクロールの状態を保持されていること。

でした。

ViewModelを使用すると、期待する動作が実現できると想定していたのですが、何故だろうという感じでした。
結局、原因はViewModelProviderを利用して、画面に対応するViewModelを生成 & 取得していなかったからでした。実装を間違えてました😞

修正後の動作

ViewModelProviderを利用すると期待通り動作したのですが、せっかくなので、ViewModelProviderの動作について少し調べてみたので、紹介したいと思います。

ViewModelProviderは、ライフサイクルを考慮して所望のViewModelを提供するクラスです。ViewModelProviderは次のコンストラクタを使って、たいてい生成すると思います。

public ViewModelProvider (ViewModelStoreOwner owner, 
ViewModelProvider.Factory factory)

引数について説明します。

  • ViewModelStoreOwner owner
    ViewModelStoreを返すインタフェースです。ViewModelStoreOwnerは、FragmentやAppCompatActivityで実装されているので、通常はthisを引数として渡します。
    ここで、ViewModelStoreはViewModelを保持するクラスです。
    ViewModelStoreは、ViewModelStoreOwner#getViewModelStoreの実行時にActivityFragmentに対応して生成されており、既にViewModelStoreが生成されている場合は、それを返却するよう実装されています。
  • ViewModelProvider.Factory factory
    引数のViewModelクラスのインスタンスを生成するための、factoryです。
    生成するViewModelのコンストラクタに引数がなければ、
    ViewModelProvider.NewInstanceFactoryを、ViewModelがAndroidViewModelを継承している場合は、ViewModelProvider.AndroidViewModelFactoryが使えます。
    また、自分で実装したViewModelProvider.Factoryを指定することもできます。手抜きの例ですが、次のように実装できます。

次にViewModelの初期化ですが、ViewModelProviderを利用して次のように行います。

val viewModel = ViewModelProvider(this, yourFactory)
.get(YourViewModel::class.java)

ViewModelProvider#getが実行されると、引数で指定されたクラスと同じ型のViewModelが、ViewModelStoreに存在するかどうかで、次の動作をします。

  • 存在する場合
    ViewModelStoreで保持していたViewModelを返します。
  • 存在しない場合
    引数で指定されたViewModelのインスタンスをViewModelProviderのfactoryにより生成して、それを返します。また、ViewModelStoreに生成したViewModelを保存します。

ということで、あるViewModelに対して、初めてViewModelProvider#getを実行すると、そのViewModelをViewModelStoreに保存します。
そして、例えば画面回転により再度、ViewModelProvider#getが実行されると、すでにViewModelStoreにあるViewModelを返します。これにより、ViewModelのデータの引継ぎが可能になっています。
但し、ViewModelStoreはViewModelStoreOwnerのライフサイクルに従うので、例えば、ViewModelStoreOwnerがFragmentの場合、バックボタンなどを押して、そのFragmentが破棄されればViewModelStoreもクリアされます(この辺りかと)。他にも、自分でViewModelStore#clearを実行すれば、クリアすることはできます。

今回は、ViewModel、ViewModelProviderについて調べたことを紹介しました。調べる際、ソースコードを色々と眺めてみましたが、さわりぐらいしか理解できませんでした。
下記の公式のページが参考になります。

--

--