Dependency Injection in Android With Dagger2 (7)

Static Provider Methods and Component Builder

Guowei Lv

2 minute read

One fact: it is more performant to make the all provider methods inside Modules static.

But for Modules that have bootstrap dependencies, this is impossible. Let’s take ActivityModule as example.

@Module
class ActivityModule(private val activity: AppCompatActivity) {

    @Provides
    fun activity(): AppCompatActivity = activity

    @Provides
    fun layoutInflater(activity: AppCompatActivity): LayoutInflater = LayoutInflater.from(activity)

    @Provides
    fun fragmentManager(activity: AppCompatActivity) = activity.supportFragmentManager

    @Provides
    @ActivityScope
    fun screensNavigator(activity: AppCompatActivity) = ScreensNavigator(activity)
}

We can easily make fun layoutInflater(), fragmentManager() and screensNavigator static methods. But we cannot do it for fun activity(), because static method does not have access to private property activity.

This means we cannot pass anything into Module’s constructor. But then where can we provide activity? The answer is ActivityComponent can provide it directly.

And in order to do this, we need some extra stuff.

First, let’s recap how ActivityComponent is created.

@AppScope
@Component(modules = [AppModule::class])
interface AppComponent {
    fun newActivityComponent(activityModule: ActivityModule): ActivityComponent
    fun newServiceComponent(serviceModule: ServiceModule): ServiceComponent
}
open class BaseActivity : AppCompatActivity() {
    
    ...
    
    val activityComponent: ActivityComponent by lazy {
        appComponent.newActivityComponent(ActivityModule(this))
    }
    
    ...
}

So basically this is how ActivityComponent created: appComponent.newActivityComponent(ActivityModule(this)).

So if now ActivityModule does not take in activity, then the only place to provide activity is ActivityComponent itself.

But as you can see, we don’t have control over the creation of ActivityComponent. So the solution to this problem is Component Builder.

@ActivityScope
@Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
    fun newPresentationComponent(): PresentationComponent

    @Subcomponent.Builder
    interface Builder {

        @BindsInstance
        fun activity(activity: AppCompatActivity): Builder

        fun activityModule(module: ActivityModule): Builder

        fun build(): ActivityComponent
    }
}

So we create ActivityComponent.Builder so that we can describe how we want to build an ActivityComponent: need ActivityModule and an instance of activity.

And once we declare this Builder interface, we cannot expose function that returns ActivityComponent directly, we need to change the return type to the Builder.

@AppScope
@Component(modules = [AppModule::class])
interface AppComponent {
    fun newActivityComponentBuilder(): ActivityComponent.Builder
    fun newServiceComponent(serviceModule: ServiceModule): ServiceComponent
} 

Now we are able to create ActivityComponent inside BaseActivity like this:

open class BaseActivity : AppCompatActivity() {

    ...
    
    val activityComponent: ActivityComponent by lazy {
        appComponent.newActivityComponentBuilder()
                .activity(this)
                .activityModule(ActivityModule)
                .build()
    }
    
    ...

}

And finally the ActivityModule looks like:

@Module
object ActivityModule {

    @Provides
    @ActivityScope
    fun screensNavigator(activity: AppCompatActivity) = ScreensNavigator(activity)

    @Provides
    fun layoutInflater(activity: AppCompatActivity): LayoutInflater = LayoutInflater.from(activity)

    @Provides
    fun fragmentManager(activity: AppCompatActivity) = activity.supportFragmentManager
}
comments powered by Disqus