Daggerを使ってViewModel
をDIするには@Multibindings
を使う方法を始めとしていくつか考えられますが、ここでは@Multibindings
やSubComponent
などは使わずに、ファクトリークラスを自分で用意する方法を、Dagger2.31で導入されたAssitedInject
も絡めてメモしておきます。
なお、この記事ではHilt
, Dagger-Android
は扱いません。
AssistedInjectを使わない実装
以下のようなカスタムファクトリークラスを自分で用意します。
class BookViewModel( private val bookId: String, private val bookRepository: BookRepository ) : ViewModel() { ... ... class Factory @Inject constructor( private val bookRepository: BookRepository ) { fun create( val bookId: String ): BookViewModel { return SampleViewModel(bookId, bookRepository) } } }
UI側では以下のようにしてBookViewModel
を生成できます。
@Inject lateinit var bookViewModelFactory: BookViewModel.Factory private val bookViewModel by viewModels<BookViewModel>( factoryProducer = { viewModelFactory { bookViewModelFactory.create("12345") } } )
ここでは簡単のためbookId
として12345
という固定値を渡していますが、実際にはSafe Args
などから取得した値を渡すことなどが多いかと思います。
なお、viewModelFactory
は次のようなメソッドです。
fun <T : ViewModel> viewModelFactory( factory: () -> T ): ViewModelProvider.Factory { return object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return factory() as T } } }
AssistedInjectを使った実装
上記の実装もいいのですが実装量は増えますし、例えばViewModel
の依存するクラスが増減した場合にはファクトリークラスの修正も必要になってしまいます。
AssitedInject
を使うとこの問題が解決できます。AssitedInject
はもともとSquareが開発しているライブラリですが、最近Dagger本体に導入されました。
DaggerのAssitedInject
を使って書き換えると次のようになります(UI側の実装は変わらないので省略します)。
class BookViewModel @AssistedInject constructor( @Assisted private val bookId: String, private val bookRepository: BookRepository ) : ViewModel() { ... ... @AssistedFactory interface Factory { fun create(bookId: String): BookViewModel } }
かなりシンプルになりました。AssistedInject
の詳しい使い方については上記リンクを参照してください。
UIからデータを渡さない場合
上記はUI側からViewModel
のコンストラクターに何かしらの値を渡すケースでしたが、値を渡さないケースも同じように実装できます。
class BookViewModel @AssistedInject constructor( private val bookRepository: BookRepository ) : ViewModel() { ... ... @AssistedFactory interface Factory { fun create(): BookViewModel } }
SquareのAssistedInject
を利用しても同様の実装ができますが、ビルド時に以下のような警告が出ます。
Assisted injection without at least one @Assisted parameter can use @Inject", targetConstructor
一方、DaggerのAssistedInject
は@Assisted
がないケースでの利用も想定されているようで、警告は出ません。
まとめ
- カスタムファクトリークラスを用意して
ViewModel
をDIする方法を紹介しました - Dagger2.31で導入された
AssistedInject
を使うことで実装をよりシンプルにできます - なお、
SavedStateHandler
を扱うViewModel
のDI方法については模索中です
使ったコードはこちらに置いています。