昨天介紹完 DI 的原理後,接下來我們開始將 Dagger 2 導入專案。
dependencies {
def daggerVersion = '2.23.2'
implementation "com.google.dagger:dagger:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
kaptTest "com.google.dagger:dagger-compiler:$daggerVersion"
}
Dagger 另外推出了 Dagger Android 為 Android 提供更好的支持,之後我們會用到,現在我們先嘗試為專案建立一個 dagger 環境。
建立一個 Class ApplicationModule
,並在 class 上加一個 annotation @Module
,用來標記此 class 負責提供注入物件給其他要被注入的 class 使用。
@Module
class ApplicationModule {
@Singleton
@Provides
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Singleton
@Provides
fun provideMainDispatcher(): MainCoroutineDispatcher = Dispatchers.Main
@Singleton
@Provides
fun provideDataBase(context: Context): ToDoRoomDatabase {
return Room.databaseBuilder(
context.applicationContext,
ToDoRoomDatabase::class.java,
"Task.db"
).build()
}
@Provides
fun provideTasksDao(db: ToDoRoomDatabase) = db.tasksDao()
}
可以看到我在 function 上還標記了 @Singleton
及 Provides
:
@Singleton
:用來標示此方法提供的物件是一個 Singleton@Provides
:表示標記的方法可以提供依賴物給其他 class 使用在這裏,我建立兩個 function 用來表示提供了兩個 CoroutineDispatcher ,分別是 Dispatchers.IO
及 Dispatchers.Main
,之後其他 class 要使用 Dispatchers 時都可以透過 @Module
標記的 class ApplicationModule
找到他們需要的依賴。
同樣的,依此類推建立了取得 Room 的 TaskDao
的方法。
除了使用 @Provides
來提供依賴實體外,我們還可以透過 constructor injection 的方式提供依賴:
class Sample @Inject constructor() {
fun sayHello() {
Log.d(this::class.java.simpleName, "Hello Dagger!")
}
}
...
@Inject
lateinit var sample: Sample
fun main() {
sample.sayHello()
}
完成提供依賴的 class 後,我們來實作注入器本身。
這邊有點長,需要慢慢理解。
首先我們建立一個 ApplicationComponent
:
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
ApplicationModule::class
]
)
interface ApplicationComponent : AndroidInjector<TodoApplication> {
@Component.Factory
interface Factory {
fun create(@BindsInstance applicationContext: Context): ApplicationComponent
}
}
@Component
:是指橋樑的意思,也可理解為提供依賴組件的 Injector ,在 Dagger 裡 Injector 要被定義可以被存放提供哪些物件,所以我們就需要往 @Component.modules
塞入定義了可以被注入的物件的 Mudule 。
Component 會被 Dagger 自動實作出來,實作出來的 Class name 為 "Dagger" + Component name ,所以這裡 Dagger 會幫我們自動實作出 DaggerApplicationComponent
這個 Injector 本體。
而 AndroidInjectionModule
則是 Dagger 提供的 Android 四大元件的注入方式。
那麼 ApplicationComponent 繼承的 AndroidInjector
是什麼呢?
這個說來話長,礙於篇幅有限無法詳細敘述,想知道原因可以看看 早期的實作方式 。簡單來說早期 Dagger 要在 Android 的元件上使用,每次都必須在一開始就先進行 init & Component inject 等操作,為此 Dagger 在 Dagger Android 提供了這些簡化方式,讓開發者不用一直造輪子。
AndroidInjector
有一個工廠方法需要實現,而這個方法本身是提供 Component 這個注入器給其他人,那究竟給誰呢?我們轉到 TodoApplication:
open class TodoApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerApplicationComponent.factory().create(applicationContext)
}
}
可以看到有別於繼承一般的 Application
,我們繼承了 Dagger 的 DaggerApplication
,並實作了其方法 applicationInjector()
,這邊我們的 DaggerApplicationComponent 創造了一個注入器給 Application 使用。
最後,我們再讓 Injector 可以提供 Activity 這個依賴本身:
@Module
abstract class ActivityBindingModule {
@ContributesAndroidInjector
abstract fun contributeTasksActivity(): TasksActivity
}
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
ActivityBindingModule::class,
ApplicationModule::class
]
)
interface ApplicationComponent
......
同樣的,早期創建 Activity 的 Injector 時需要寫很多重複的程式碼去宣告要把 Activity 的物件交給 Component 分配,現在 Dagger 提供 @ContributesAndroidInjector
來自動創建這些程式。
最後實際使用的樣子:
class MainActivity : AppCompatActivity() {
@Inject
lateinit var sample: Sample
@Inject
lateinit var db: Task
......
override fun onCreate(savedInstanceState: Bundle?) {
// 注意!!!
// AndroidInjection.inject(this) 需在 super.onCreate 之前宣告注入
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sample.sayHello()
db.getTasks()
......
}
}
今天先大致把 Dagger 建立起來,明天會再繼續對 Dagger 改造。