Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

12. Components Dependencies
Written by Massimo Carli

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

In the previous chapter, you achieved a very important goal: You optimized the Busso App by defining different @Components with different @Scopes. Using the existing @Singleton as well as custom @ActivityScopes and @FragmentScopes, you gave each object the right lifecycle, optimizing the app’s resources. In the process, you had the opportunity to experience what component dependency means.

In this chapter, you’ll learn even more about @Components and dependencies. In particular, you’ll learn:

  • Why @Singleton isn’t so different from the other @Scopes.
  • Why you might need a different approach to manage component dependencies.
  • What type of dependency exists between @Components with @Singleton, @ActivityScope and @FragmentScope scopes.
  • How to use @Subcomponent.Builder and @Subcomponent.Factory and why you might need them.
  • When and how to use the @Reusable annotation.

You still have a lot to learn.

Comparing @Singleton to other scopes

So, is @Singleton really something special? As you saw in the previous chapter, @Singleton is like any other @Scope. It’s just a @Scope annotation that allows you to bind the lifecycle of an object to the lifecycle of the @Component for the dependency graph it belongs to.

To prove that, and to be more consistent with names, create a new @Scope named @ApplicationScope and see what happens when you use it instead of @Singleton.

Create a new file named ApplicationScope.kt in the di.scopes package and add the following code:

@Scope
@MustBeDocumented
@Retention(RUNTIME)
annotation class ApplicationScope

The only difference between @ActivityScope and @FragmentScope is the name.

Now, open ApplicationComponent.kt in di and replace @Singleton with @ApplicationScope:

@Component(modules = [ApplicationModule::class])
@ApplicationScope // HERE
interface ApplicationComponent {
  // ...
}

Now, you have to do the same in LocationModule.kt in di:

@Module
class LocationModule {
  @ApplicationScope // HERE
  @Provides
  fun provideLocationManager(application: Application): LocationManager =
      application.getSystemService(Context.LOCATION_SERVICE) as LocationManager

  @ApplicationScope // HERE
  @Provides
  fun providePermissionChecker(application: Application): GeoLocationPermissionChecker =
      GeoLocationPermissionCheckerImpl(application)
  // ...
}

Finally, apply the same changes in NetworkModule.kt in network:

@Module
class NetworkModule {
  @Provides
  @ApplicationScope // HERE
  fun provideCache(application: Application): Cache =
      Cache(application.cacheDir, 100 * 1024L)// 100K

  @Provides
  @ApplicationScope // HERE
  fun provideHttpClient(cache: Cache): OkHttpClient =
      Builder()
          .cache(cache)
          .build()

  @Provides
  @ApplicationScope // HERE
  fun provideBussoEndPoint(httpClient: OkHttpClient): BussoEndpoint {
    //...
  }
}

Now, you can build and run Busso to prove that nothing’s changed, other than using more consistent naming in the app’s @Scopes.

Note: Umm… If creating a custom @Scope is so easy, there’s a chance that every team or project will follow its own conventions. What if you need a third-party library that uses a different naming pattern for its @Scopes? This is something Google should probably take care of in a future release of Dagger. :]

Component dependencies

Using the dependencies attribute of @Component lets you add objects from one dependency graph to the objects of another. It’s a kind of graph composition. If @Component A needs some objects that are part of the dependency graph of @Component B, you just add B as one of the dependencies for A.

Figure 12.1 — @Component dependencies
Devuja 57.2 — @Sopdigicg putansupyuiw

Figure 12.2 — @Subcomponent dependencies
Lovepe 14.2 — @Todguqqunick qopejcomniep

Your scoped Busso App

As mentioned in the introduction, you achieved a very important goal in the previous chapter: Giving each object of the Busso App a proper @Scope. You implemented this by creating three different @Components:

Figure 12.3 — @ApplicationComponent and @ActivityComponent dependencies
Suyedu 33.1 — @EbqqivefaovSofzuvegr izn @OmmepompWosgisolh zivicxinvair

Figure 12.4 — @FragmentComponent dependencies
Bifaze 24.5 — @BxadkopdZuqmenojz wexoxperyoec

Using @Subcomponents

So, you’ve just learned in detail how the objects of different @Components of the Busso App depend on one other. You also learned that Busso requires some kind of inheritance relationship between the @Components because you want to use the objects from the ApplicationComponent in the ActivityComponent and in the FragmentComponent. You also want to use the objects of the ActivityComponent in the FragmentComponent.

Figure 12.5 — @Component inheritance
Monovi 17.8 — @Jelkicard etsadaluxpi

Migrating @Components to @Subcomponents

Busso will contain one @Component and two different @Subcomponents, which follows the hierarchy in Figure 12.5. Open ActivityComponent.kt from di and apply the following change:

// 1
@Subcomponent(
    modules = [ActivityModule::class]
    // 2
)
@ActivityScope
interface ActivityComponent {
  // ...
}
// 1
@Subcomponent(
    modules = [FragmentModule::class]
    // 2
)
@FragmentScope
interface FragmentComponent {
  // ...
}

Creating the @Subcomponent relationship

For your next step, you need to tell Dagger that:

@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {

  fun activityComponent(): ActivityComponent // HERE

  // ...
}
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {

  fun activityComponentFactory(): ActivityComponent.Factory // HERE
  // ...
}
@Subcomponent(
    modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
  // ...
  fun fragmentComponent(): FragmentComponent // HERE
}

Using @Subcomponent.Builder & @Subcomponent.Factory`

Dagger now understands the relationship between Busso’s @Components. Before you go any farther, however, you need to do some clean-up. Start by opening FragmentComponent.kt in di and change it like this:

@Subcomponent(
    modules = [FragmentModule::class]
)
@FragmentScope
interface FragmentComponent {

  fun inject(fragment: BusStopFragment)

  fun inject(fragment: BusArrivalFragment)
}
@Subcomponent(
    modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
  // ...
  @Subcomponent.Factory // 1
  interface Factory {
    fun create(
        @BindsInstance activity: Activity
        // 2
    ): ActivityComponent
  }
}

Using @Subcomponent’s generated code

Dagger should now be able to happily generate all the code to create:

Updating how you use ApplicationComponent

The first place you need to check is Main.kt in the app’s main package. This is where you create the ApplicationComponent instance.

class Main : Application() {

  lateinit var appComponent: ApplicationComponent

  override fun onCreate() {
    super.onCreate()

    appComponent = DaggerApplicationComponent
        .factory()
        .create(this)
  }
}

val Context.appComp: ApplicationComponent
  get() = (applicationContext as Main).appComponent

Updating how you use ActivityComponent

You create a new instance of the ActivityComponent in the following places:

class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    application.appComp // 1
        .activityComponentFactory() // 2
        .create(this) // 3
        .inject(this) // 4
    splashViewBinder.init(this)
  }
  // ...
}
class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    comp = application.appComp // HERE
        .activityComponentFactory()
        .create(this)
        .apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

Updating how you use FragmentComponent

The two places you create FragmentComponents are:

class BusStopFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    context.activityComp // 1
        .fragmentComponent() // 2
        .inject(this) // 3
    super.onAttach(context)
  }
  // ...
}
class BusArrivalFragment : Fragment() {
  // ...
  override fun onAttach(context: Context) {
    context.activityComp // HERE
        .fragmentComponent()
        .inject(this)
    super.onAttach(context)
  }
  // ...
}
Figure 12.6 — The Busso App
Tudane 53.6 — Wna Misgu Oyw

Cleaning up the factory methods

Using @Subcomponents, you discovered a new way to implement a @Component dependency. Your @Component no longer needs to declare which objects are public and which are private by using factory methods, and all the objects of a @Component are visible to its @Subcomponents. This allows you to remove the factory methods from:

@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {

  fun activityComponentBuilder(): ActivityComponent.Builder

  @Component.Factory
  interface Builder {

    fun create(@BindsInstance application: Application): ApplicationComponent
  }
}
@Subcomponent(
    modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {

  fun inject(activity: SplashActivity)

  fun inject(activity: MainActivity)

  fun fragmentComponent(): FragmentComponent

  @Subcomponent.Factory
  interface Factory {
    fun create(
        @BindsInstance activity: Activity
    ): ActivityComponent
  }
}

Using @Subcomponent.Builder

In the previous example’s ActivityComponent, you used @Subcomponent.Factory, but you could have used @Subcomponent.Builder, instead. To prove this, open ActivityComponent.kt from di and apply the following change:

@Subcomponent(
    modules = [ActivityModule::class]
)
@ActivityScope
interface ActivityComponent {
  // ...
  @Subcomponent.Builder // 1
  interface Builder {
    // 2
    fun activity(
        @BindsInstance activity: Activity
    ): Builder
    // 3
    fun build(): ActivityComponent
  }
}
@Component(modules = [ApplicationModule::class])
@ApplicationScope
interface ApplicationComponent {
  // ...  
  fun activityComponentBuilder(): ActivityComponent.Builder // HERE
  // ...
}
class SplashActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    makeFullScreen()
    setContentView(R.layout.activity_splash)
    application.appComp
        .activityComponentBuilder() // 1
        .activity(this) // 2
        .build() // 3
        .inject(this)
    splashViewBinder.init(this)
  }
  // ...
}
class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    comp = application.appComp
        .activityComponentBuilder() // HERE
        .activity(this)
        .build()
        .apply {
          inject(this@MainActivity)
        }
    if (savedInstanceState == null) {
      mainPresenter.goToBusStopList()
    }
  }
}

@Subcomponents versus @Component dependencies attribute

In the last two chapters, you learned how to use @Components with different @Scopes. You also learned two different ways of managing dependencies between objects with different @Scopes using:

Using @Reusable

In this and the previous chapters, you learned a lot about the concepts of @Component and @Scope. In particular, you learned how to create a custom @Scope that’s not much different than the existing @Singleton.

@Documented
@Beta
@Retention(RUNTIME)
@Scope
public @interface Reusable {}
Figure 12.7 — Using @Reusable scope
Misina 54.7 — Uhixz @Peatogci lkuhi

Key points

  • @Component dependencies are a fundamental concept you should master when using Dagger.
  • Using the @Component dependencies attribute requires you to explicitly expose the objects you want to share using factory methods. The dependency is not transitive.
  • @Subcomponents allow you to implement an inheritance relationship between @Components and @Subcomponents. In this case, you don’t need to use a factory method. Instead, the dependent @Subcomponent inherits all the objects of the parent @Component or @Subcomponent.
  • To provide existing objects to a @Subcomponent, use @Subcomponent.Factory or @Subcomponent.Builder.
  • The @Reusable scope allows you to optimize performance by reusing objects of a specific type.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now