iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 14
0
自我挑戰組

Android Architecture 及 Unit Test系列 第 14

[Day 14] Dagger 2:Part 2 Basic

  • 分享至 

  • xImage
  •  

昨天介紹完 DI 的原理後,接下來我們開始將 Dagger 2 導入專案。

Gradle

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 環境。

Module 、 Singleton & Provides

建立一個 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 上還標記了 @SingletonProvides

  • @Singleton :用來標示此方法提供的物件是一個 Singleton
  • @Provides :表示標記的方法可以提供依賴物給其他 class 使用

在這裏,我建立兩個 function 用來表示提供了兩個 CoroutineDispatcher ,分別是 Dispatchers.IODispatchers.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 後,我們來實作注入器本身。

Component

這邊有點長,需要慢慢理解。

首先我們建立一個 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 改造。


上一篇
[Day 13] Dagger 2:Part 1 DI with Dagger
下一篇
[Day 15] Dagger 2:Part 3 Complete Dagger
系列文
Android Architecture 及 Unit Test30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言