Dagger 2 + Kotlin 那一兩件事情
前言
上回我提到了 Inversion of Control ,詳細可以看我寫的上一篇文章,聽起來很酷很難,但技術嘛,講開了明白了就不會覺得困難了,實作起來其實不難,但是由於在文章裡頭寫的往往是暸解概念用的,通常都是將需求理想化或者是直接排出抑或是省略了大多數障礙得到的結果,這也不是問題,因為身為一名優秀的工程師,就是能夠在了解概念後,根據現場情況的不一去做相對的處理。
ohmmmm….雖然身為一名優秀的(?) Android 開發者,但是在處理依賴注入這檔事情上,還是很容易碰上問題。
例如說我要針對 Activity 做 Injection 的動作,因為 Android app 特性的問題,不可能實作 Activity 的 constructor (Fragment 還有可能,但是也會巧妙的產生很多的問題),那可能因此要用到反射(Refraction),或者包裝起來拿到外面用靜態去注入 blabla 之類的邪淫巧技,為此花上了不少時間,然後因為是自幹的所以沒有相關的學習管道,要不生成一份學習文件要不就親自去教人,最糟的情況是放著啥都沒幹幾個月後自己也忘記自己當初為啥要這麼幹,最終變成一個無人解讀的遠古魔法書。
所以用很潮的 Library 好處就出現了,因為這個 Library 很潮,絕對不會只有自己一個人用過,然後因為 Library 很潮有自己的官網甚至還有完整的教學文件,很棒,用了只要稍微解釋一下自己要拿來解決什麼問題,然後就丟全是英文的官方文件要接手的人讀,享受看著對方因為對英文閱讀多少有障礙但是礙於自己 Title 掛著工程師而不能明講而掙扎的狀態
#゚Å゚)⊂彡☆))゚Д゚)・∵
總之前面幹話這麼多,就是為了省去自幹一套 Dependency Injection Framework 找藉口。
ヽ(゜▽゜)-C<(/;◇;)/~[不要拖我自己走!]
總之先來個前置作業吧
DI Framework 最著名的應該就是 Dagger 了,這個 Framework 除了因為它可以實現 DI 以外,更著名的是因為它很難學。
並不是說他難用,而是因為…呃…首先是因為沒有 DI 概念,所以知道它也不知道該用來幹什麼,用他有什麼好處;再來是因為從零開始,他的操作看起來很像魔法,這邊 Inject 一下,那邊 Inject 一下,設個 Component ,再 create 一下 inject 一下,呼隆一下東西就出來了。
這邊是寫在 Android 上,那就是直接上:
implementation ‘com.google.dagger:dagger:2.17’
另外我這邊是用 Kotlin ,跟在 Java 的時候不太一樣要用 kapt:
kapt 'com.google.dagger:dagger-compiler:2.17'
直接來個例子好了,我這有一個 Activity,我希望對他注入一個狗的物件,好讓他顯示狗的圖片外加跳出一個狗的叫聲:
那首先在 CreatureActivity 加入一個狗的物件然後上頭加上一個 @Inject 的 Annotation。
那狗狗得物件也得在 constructor 上加上 @inject
那還會需要一個 @Component 藉此定義 CreatureActivity 的 Injection
然後 build 一下 Project 。
最後再來個注入的動作,這樣就好了,來試一下結果。
很好,一切運作如我所料,完美無缺。
hmm…所以我剛才到底幹了什麼?
感覺就像是在地上畫一個六芒星招喚陣,拿起魔法書照著咒文念一下,碰喳一陣煙惡魔就冒出來了。
@Inject & @Component
稍微解釋一下這些程式碼的目的唄。
這邊這個 @Inject 目的是要標明這個 class 需要注入的依賴對象
那麼這邊這個 @Inject 的目的是為了標明這個物件為一個依賴對象
這個則是要標明要給 Dagger auto generate 的注入動作元件,因為他會透過 @Component 定義的 class 去找需要 Inject 的物件,所以得用 CreatureActivity 不能用 Activity
在 Make Project 之後可以從動態的 build 資料夾裡面找到 generate 出來的 class
Factory 內容比較沒啥,就一個靜態的 method 可以用來生成 Provider ,比較需要 DaggerCreatureComponent ,可以打開來看看
其實可以看出來他這邊將 inject 進來的 Activity 做了物件封裝的動作,那顯然這只是一個注入的動作,還是得要指定一個地方去實作他。
就是這樣了。
這邊程式碼讀起來很有意思,看起來就像是 CreatureActivity 對自己 Inject 了,不過這跟我為了方便所以把 Code 這樣寫 + Activity 本身有 Controller 功用的平台特性有關,這又是另一個故事了。
那這樣, Dagger 就講完了嗎?
當然沒有啊,不然怎麼會有一堆人跳坑又退坑啊哈哈哈哈哈….咳咳
@Provides & @Module
那再接續方才的例子,今天我希望能夠變成一個是狗在叫,另一個是貓在叫,當然我可以生成兩個 Activity ,但是很明顯兩個 Activity 行為相同,沒有必要再為此生成另一個 Activity ,所以我期望能用 Dagger 解決這問題。
那首先是得先將形象和叫聲字樣抽象化
然後更正 CreatureActivity 的 Inject Project
因為 interface 本身沒有 constructor,也無法直接被建構,顯然 Dagger 不會通靈,他不可能知道要 inject 的 interface 實際會是什麼,所以這個時候如果編譯了,會報錯:
畢竟 inject 能夠標示的只有清楚實作的物件,這時候如果換作是抽象型別,就不能只單用 inject 了。
這時候就該 Provides 和 Module 出馬了 。
這邊新加一個 class 叫 CreatureModule ,裡面有一個 method 回傳 Creature 類別,並標上 Provides,那一個 Provides 必然屬於一個 Module ,那就是這個 CreatureModule ,所以替它標上 Module。
那可以看到我在這邊 的 method 把 interface 的實作都寫進去了,這樣在 可以根據我實作這個 class 傳入的數值得到不同的實作。
那一樣的,Dagger 不會通靈,他不可能在你沒指定的狀況下去找到這個 Module 自動去用它,所以需要再去 Component 指定要使用該 Module
然後就可以讓 Dagger generate code 了。
build 出來之後可以看一下
那可以發現 build 這段會要求傳入 CreatureModule ,否則在 Run time 時會噴 Exception,因為我這邊的設計只有一個 constructor 而且必須傳入數值,就會產生這樣的 Dagger Component,如果有預設一個 constructor 不用傳進任何數值的話,產生出來的 Code 會變成這樣
可以發現它會自動給一個預設值。
不過這邊我依照第一個自動產生的方式去做,因為我在傳遞上會有值。
那在做 Inject 的時候把值餵給 builder 就可以了
這樣這個 Activity 就可以根據 intent 塞入的值而有所不同了。
PS:其實應該看得出來,這邊的做法有點為用而用,所以多少顯得有點多餘,只是因為這篇文章的目的是要展現 Dagger 怎麼實作,所以才會有這種為用而用的程式碼出現,實務上可以避免請盡量避免。
結語
講到這邊大概是 Dagger 文件的一半,也大概是 Dagger 2 的基本用法了。
此外還有 @Singleton @Reusable @Qualifier 等 Annotation 可以用,這個用法又更進階了,我這邊暫時想不到 Case 可以用的這麼複雜(笑)。所以就擇日再提唄。
如果有任何意見、問題、或者是對我的論點有質疑想要反駁,希望各位不要吝嗇提出疑惑對我多多指教。
最後附上我本篇的範例 Code: