Dagger 的強大遠遠超過我們昨天介紹的範疇,如果 dagger 基本教學還無法滿足大家實務上的需求,我們今天就更進一步來繼續討論 dagger 還有什麼特別的功能吧。
如果你還沒看過我們的 dagger 基礎教學,請見:[Android 十全大補] Dagger
第一個可能遇到的問題是要怎麼區別同樣型別的 dependency,假設有以下二種 paper 的提供方式:
@Provides
fun provideMyPaper(wood: Wood): Paper {
return MyPaper(wood)
}
@Provides
fun provideDoubleAPaper(wood: Wood): Paper {
return DoubleAPaper(wood)
}
我們有提過 function 名稱並不重要,重點是回傳的 type ,所以如果有任何地方需要 paper 物件,我們要怎麼區別需要的是 MyPaper
還是 DoubleAPaper
呢?
這時候就是 Qualifier
派上用場的地方了,我們這邊使用 Named
這種以 string 為判斷的 Qualifier
,回頭幫我們的 providePaper
跟 provideDoubleAPaper
加上 Named
annotation 如下:
@Named("normal")
@Provides
fun providePaper(wood: Wood): Paper {
return MyPaper(wood)
}
@Named("doubleA")
@Provides
fun provideDoubleAPaper(wood: Wood): Paper {
return DoubleAPaper(wood)
}
然後同樣道理,我們可以在我們的 providePrinter
參數上指定我們所要的 Named
:
@Provides
fun providePrinter(@Named("doubleA") paper: Paper, ink: Ink): Printer {
return MyPrinter(paper, ink)
}
這樣就可以輕易的處理多種同型別的不同 dependency,如果覺得 string 不好看的話,也可以試著自己建立 Qualifier
,一樣的例子我們可以改成以下的程式碼:
@Qualifier
annotation class DoubleA
@Qualifier
annotation class NormalPaper
@NormalPaper
@Provides
fun providePaper(wood: Wood): Paper {
return MyPaper(wood)
}
@DoubleA
@Provides
fun provideDoubleAPaper(wood: Wood): Paper {
return DoubleAPaper(wood)
}
@Provides
fun providePrinter(@DoubleA paper: Paper, ink: Ink): Printer {
return MyPrinter(paper, ink)
}
有了 DoubleA
跟 NormalPaper
二個 annotation,我們的程式碼是不是漂亮許多呢?
學過 Android 的人應該會覺得生命週期是個很重要的主題,對 dagger 來說也是如此,scope 是一個讓我們識別不同生命週期的 annotation,我們可以試著建立一個 scope 如下:
@Scope
annotation class MyScope
接下來試著幫我們的 providePrinter 加上 MyScope
這個 annotation:
@MyScope
@Provides
fun providePrinter(@DoubleA paper: Paper, ink: Ink): Printer {
return MyPrinter(paper, ink)
}
compile 之後會發現我們也必須將對應的 PrinterComponent
加上一樣的 scope:
@MyScope
@Component(modules = [PrinterModule::class, InkModule::class, PaperModule::class])
interface PrinterComponent {
fun inject(mainActivity: MainActivity)
}
現在可以成功的 compile 了,但是地球還是繞著太陽轉,其實 scope 只是一個幫助我們識別生命週期的 annotation。
雖然 compiler 會依據這個 annotation 幫我們做對應的檢查,但 MyScope
並沒有其他的額外資訊來描述生命週期的訊息,真正的生命週期是跟著 PrinterComponent
的實體走的。
所以不管你是使用 MyScope
或是 Singleton
,都要自己好好管理喔!
假設今天我們的 PrinterModule
越來越複雜,我們想要獨立出 InkModule
來處理墨水相關的 dependency,在 dagger 要怎麼處理呢?
我們把墨水相關的 @Provides
function 都移動到新增的 InkModule
裡如下:
@Module
class InkModule {
@Provides
fun provideInk(water: Water): Ink {
return MyInk(water)
}
@Provides
fun provideWater(): Water {
return MyWater()
}
}
回到 PrinterComponent
,我們會發現 @Component
的參數 - modules
給了我們線索,這邊是可以放多個 module 的,我們把 InkModule
加到 modules
的清單中:
@Component(modules = [PrinterModule::class, InkModule::class])
interface PrinterComponent {
fun inject(mainActivity: MainActivity)
}
這樣就完成了,compile 看看是否有任何問題吧!
除了多個 module 間的連結外 Component 之間也可以做連結,假設我們把 InkComonent
也拆分出來成為獨立的檔案:
@Component(modules = [InkModule::class])
interface InkComponent {
fun getInk(): Ink
}
這樣好處是我們可以用 InkComponent
來限制 InkModule
被外界存取,現在只有 getInk
可以對外,所以外界是無法直接拿到其他諸如 Water
等的 dependency 的。
另一方面,我們必須回頭修改 PrinterComponent
如下:
@Component(dependencies = [InkComponent::class], modules = [PrinterModule::class])
interface PrinterComponent {
fun inject(mainActivity: MainActivity)
}
我們透過 dependencies 宣告 PrinterComponent
必須依賴於 InkComponent
的存在,感覺上這樣就完成了,不過 compile 之後會在 MainActivity
發現一個錯誤,PrinterComponent
因為有外界 dependency 存在,所以無法使用 DaggerPrinterComponent.create()
來得到 PrinterComponent
,我們修改如下:
val inkComponent = DaggerInkComponent.create()
DaggerPrinterComponent
.builder()
.inkComponent(inkComponent)
.build()
.inject(this)
這樣就完成多個 component 的組成了,是不是很神奇呢!
以上就是今天的內容了,除了我們所分享的內容外,dagger 還可以做更多複雜的操作,module 的 includes,Subcomponent 等都是很有意思的主題,大家可以多多探索,甚至去看 dagger 的 source code 也會讓大家對 dagger 或 annotation processing 更了解喔。
希望 dagger 這把小刀大家會越用越順手!我們明天見!
Android 十全大補已經正式出書上架囉!
有興趣的讀者歡迎參考:
https://www.tenlong.com.tw/products/9789864345786