LoginSignup
28
4

More than 3 years have passed since last update.

【Android】Single ActivityにおけるsharedViewModelのスコープについて【Koin】

Posted at

はじめに

KoinでViewModelをインジェクトする際には、viewModel()sharedViewModel()の二つのやり方がある。
僕が担当しているアプリでは基本Fragment-ViewModelで一対一の関係になっているのだけど、ViewPagerを使用していたりするとViewModelをFragment間で共有したい場面が多くて、そういう時にsharedViewModel()が大活躍してくれる。

ただ、今までなんとなくsharedViewModel()を使ってきてしまったが故に、こないだちょっと沼にハマってしまった。
今回はそんなsharedViewModel()で共有したViewModelのスコープについてご紹介。

sharedViewModelとは?

前述の通り、ViewModelをFragment間で共有できるViewModelをインジェクトする。
と、今まで思っていた。

というのも実はこのsharedViewModel()、インスタンスを共有するスコープがデフォルトでActivityになっているらしい。
中の実装をチラッと覗いてみると、こんな感じになっている。

/**
 * Lazy getByClass a viewModel instance shared with Activity
 *
 * @param qualifier - Koin BeanDefinition qualifier (if have several ViewModel beanDefinition of the same type)
 * @param from - ViewModelStoreOwner that will store the viewModel instance. Examples: "parentFragment", "activity". Default: "activity"
 * @param parameters - parameters to pass to the BeanDefinition
 */
inline fun <reified T : ViewModel> Fragment.sharedViewModel(
        qualifier: Qualifier? = null,
        noinline from: ViewModelStoreOwnerDefinition = { activity as ViewModelStoreOwner },
        noinline parameters: ParametersDefinition? = null
): Lazy<T> = kotlin.lazy { getSharedViewModel<T>(qualifier, from, parameters) }

見て欲しいのは、第二引数のfromのところ。
コメントでViewModelStoreOwner that will store the viewModel instance. Examples: "parentFragment", "activity". Default: "activity"とある通り、デフォルトではViewModelのインスタンスを保存している場所がactivityとなっている。

そのため、「ViewModelをFragment間で共有したい時に使用する」という認識は誤りで、「Activity内で共有できるViewModelをインジェクトする」という認識の方が正しかったりする。

スコープを正しく認識していないことによる問題

sharedViewModel()のスコープをきちんと認識できていなかったことがわかったわけだけども、これが原因であるバグが発生していた。

それが、下の画像のパターン。
こんな感じでFragmentAFragmentBが乗っている画面を作りたくて、二つのFragmentでViewModelを共有するためにsharedViewModel()を使用していた。

で、この画面が実は下の画像のように、同じ画面に遷移する導線を持っていた。

この時ViewModelは画面遷移の際に新たに生成される、と思っていた

実はこのViewModel、sharedViewModel()を使っているせいで、画面遷移をしても同じインスタンスのViewModelが使用されている。
自分のプロジェクトはSingleActivityで作られているので、基本的にアプリが起動している間ずっとViewModelのインスタンスが破棄されない状態になっていた。
このままでは、前のデータが残っていたり無駄にインスタンスが残っていたりしてしまう。

解決方法

解決方法はそんなに難しくない。
ViewModelを保持しておくスコープを、各Fragmentにしてあげればいいだけ。

さっき見たsharedViewModel()の第二引数のデフォルト引数が{ activity as ViewModelStoreOwner }となっていたので、こいつを変えてあげる。

private val viewModel: ViewModel by sharedViewModel()

こいつを

private val viewModel: ViewModel by sharedViewModel(from = { requireParentFragment() })

こう!

まとめ

SingleActivityでアプリを作っている場合は、スコープをActivityにしたい場面ってあんまりないと思うので、もしかしたら基本parentFragmentを指定してあげるのがいいのかもしれない。
Daggerもそうだけど、DIは設定が難しくてなんとなく使っちゃいがちだけど、やっぱりこういうのきちんとドキュメント読まなきゃだめだね。

おわり。

28
4
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
28
4