この記事は
Daggerって最近知ったんだけど、なんかよくわからない!なんかむずかしそう!と思っている方向けです。
この記事を読むと、「Daggerきいたことある」から「Daggerちょっと知ってる」になれます。
実は筆者である私もDaggerが非常に苦手なのですが、最近少し勉強したのでここにまとめていきたいと思います!
⚠️ この記事のサンプルコードは簡易化しているため実際に動くわけではありません
Daggerとは
DaggerはDIライブラリです!
DIってなに??
DIとはDependency Injectionの略で、依存性の注入を意味します!
つまりDaggerは依存性の注入(DI)を行うためのライブラリです。
とはいえ、別にDaggerがないとDIができないというわけではありません。
例えば、ニュースアプリを題材に考えてみましょう。
次のようなソースコードをみなさんもどこかで見た経験があるかと思います。
class NewsListViewModel(
private val newsRepository: NewsRepository,
): ViewModel(){
}
class NewsRepository(
private val newsDataSource: NewsDataSource,
){
}
class NewsDataSource(
private val newsApi: NewsApi,
){
}
これらのクラスはコンストラクタで依存性を注入しています。
これも一種のDIで、コンストラクタインジェクションといいます。
あるクラスを他のクラスで直接インスタンス生成することを避けていて
- テストがしやすい
- interfaceに依存させている場合、実装を差し替えるのが容易
などの利点があります。
じゃあ別にDagger使わなくてもよくね〜〜〜?
たしかに、DaggerがなくてもDIできるなら、Daggerを使う必要はないかのように思えます。
Daggerを使わない場合どうなるのでしょうか。
class NewsListActivity() {
lateinit var viewModel: NewsListViewModel
override fun onCreate() {
val newsApi = Retrofit.Builder().baseUrl()
val newsDataSource = NewsDataSource(newsApi)
val newsRepository = NewsRepository(newsDataSource)
// AACのViewModelはこのように直接Activityなどでインスタンス生成するのはNG
// サンプルなので直接インスタンス化してるだけです、本当はダメ!
viewModel = NewsListViewModel(newsRepository)
}
}
ActivityでviewModelのデータを使いたいだけなのに、NewsApiをインスタンス化し、そのインスタンスをNewsRepositoryに渡してNewsRepositoryをインスタンス化し、そのインスタンスを利用してやっとViewModelのインスタンスが作れました。ふぅ。
これだけでも大変そうですが、例えば
- ViewModelが複数のRepositoryに依存しだしたら?
- Repositoryが複数のDataSourceに依存しだしたら?
- Repositoryのインスタンスをアプリ内で一つだけ(シングルトン)にしたくなったら??
これでは対応がつらい!
Daggerパイセンたすけてください!
Dagger Hiltを使うとあっというまにコードがすっきり!
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideNewsApi(): NewsApi {
val retrofit = Retrofit.Builder.baseUrl()
return retrofit.create(NewsApi::class.java)
}
}
Daggerでインスタンスを生成してほしいとき
-
@Module
をつけたクラス内で@Provides
アノテーションによりインスタンスを配布する方法 - インスタンスを生成してほしいクラスのコンストラクタに
@Inject
アノテーションをつける方法
があります。
サードパーティーライブラリを使う場合など、直接クラスのコンストラクタを変更できない場合は1のパターンでDIします。
前述のサンプルコードはRetrofitというサードパーティライブラリを使用しているため、@Provide
でインスタンスを返しています。
@HiltViewModel
class NewsListViewModel @Inject constractor(
private val newsRepository: NewsRepository,
): ViewModel(){
}
@Singleton
class NewsRepository @Inject constractor(
private val newsDataSource: NewsDataSource,
){
}
@Singleton
class NewsDataSource @Inject constractor(
private val newsApi: NewsApi,
){
}
一方、こちらは自作のクラスなので直接コンストラクタを変更できます。
このような場合は@Inject
を使う2のケースでDIします。
また、シングルトンにしたい場合は@Singleton
をつけます。
ViewModelには@HiltViewModel
をつけてあげるのも必須です。
@AndroidEntryPoint
class NewsListActivity() {
private val viewModel: NewsListViewModel by viewModels()
}
Dagger Hiltを使う場合、Dagger Hiltで依存性が注入されるActivity/Fragment/Serviceなどに@AndroidEntryPoint
のアノテーションを付ける事が必須です。
Daggerは、各クラスの依存関係のグラフを内部的に作って、依存関係ツリーの最下層から順番にインスタンスを作成してくれます。
今回の場合だと、インスタンスを生成してくれる順番は次の通りです。
NewsApi -> NewsDataSource -> NewsRepository -> NewsListViewModel
アノテーションを正しく付与することで、Daggerパイセンが適切な順番で適切なインスタンスを適切なクラスに勝手に配布してくれます!(優秀!)
逆に人間側がアノテーションを正しく付与しないと、依存関係が解決できず、 Daggerのエラーが起きてしまいます。
[小話] ちょっと待って!Hiltってなに?!
いやDaggerの解説をしてくれる記事だと思って読んでいたけど途中からHilt(Dagger Hilt)ってのが出てきちゃってんだが。
大丈夫そ??...と、思っている方がいるかもしれませんね。(とてもよくわかりますヨ)
実は、Daggerナニモワカラナイな時、私の身にもよくそんなことが起きていました。。。
〜先輩に質問中〜
私「Daggerって〜〜〜〜〜なんですか?」
先輩「そうですね!ま、DaggerっていうかHiltはそう、って感じですけど!」
私「(は???DaggerとHiltって違うんですか???)」
私「(てかHiltってなにです???)」
〜〜〜回想終了〜〜〜
Daggerとは、javaの世界でDIするためのライブラリです。
Dagger Android Supportというのもあって、これはライフサイクルなどAndroid独自のルールが考慮されており、Daggerと組み合わせて使うものです。しっかしこれはひっじょーーーーーに扱いがむずかしいです。
そこでDagger Hiltくんが登場します。
Dagger Hiltは、Dagger Android Supportをより簡単に使えるようにしたもので、Daggerライブラリを使って構築されたAndroid用の便利なDIライブラリなのです(参考)。
なのでAndroid開発でDIしたい場合、Dagger Hiltを使うことがおすすめされますし、Dagger Android Supportを使っている場合はDagger Hiltに移行するのが良いでしょう。
(この記事でも、Dagger Hiltを前提に話を進めております。)
[蛇足] クラスじゃなくてインタフェースを配りたいとき
@Module
をつけたクラス内で@Provides
する以外にも@Binds
を使う方法もあります。(よく知らない)
参考:@Binds を使用してインターフェース インスタンスを注入する
まとめ
- DaggerはDIライブラリ
- DIとは依存性の注入のこと
- アノテーションを付与することで「このクラスはこうやってインスタンスを作ってね!」とDaggerに教えてあげる
- あとはDaggerが依存関係の順番通りにインスタンスを作ってくれるぞ!