Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

9. More About Modules
Written by Massimo Carli

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapter, you learned some important concepts about Dagger @Modules. You saw how to split the bindings for your app into multiple files using abstract classes, objects, companion objects and interfaces. You learned when and how to use the dagger.Lazy<T> interface to improve performance. And you used the Provider<T> interface to break cycled dependencies.

In this chapter you’ll learn :

  • The benefits of using the @Binds annotation in a @Module.
  • How to provide existing objects. The Android Context is a classical example.
  • When optional bindings can help.
  • How to provide different implementations of the same abstraction using qualifiers with the @Named annotation.
  • When to create custom qualifiers to make the code easier to read and less error-prone.
  • How Android Studio can help you navigate the dependency tree.

As you can see, there’s still a lot to learn about @Modules.

Note: In this chapter, you’ll continue working on the RaySequence app but by the next one, you’ll have all the information you need to migrate the Busso App to Dagger.

More about the @Binds annotation

You’ve already learned how to use @Binds to bind an abstract type to the implementation class Dagger considers fit for that type. But @Binds has other benefits, as well. You’ll see proof of that next.

Open AppModule.kt and replace the existing code with the following:

@Module(includes = [AppBindings::class])
class AppModule {

  @Provides
  fun provideSequenceGenerator(): SequenceGenerator<Int> =
      FibonacciSequenceGenerator()
}

Here, you’re using FibonacciSequenceGenerator because it has a default constructor. You’ll see how to deal with the NaturalSequenceGenerator in a @Binds scenario later in the chapter.

Now,look at the di package in build/generated/source/kapt/debug, as shown in Figure 9.1:

Figure 9.1 — Generated code with @Provides in @Module
Figure 9.1 — Generated code with @Provides in @Module

As you see, there are two different files:

  • AppModule_ProvideSequenceGeneratorFactory.kt, with 29 lines of code.
  • DaggerAppComponent.kt, with 73 lines of code.

You also know that you can provide an instance of the SequenceGenerator<Int> implementation using @Binds. Replace the previous code with the following:

@Module(includes = [AppBindings::class])
interface AppModule {

  @Binds
  fun bindsSequenceGenerator(impl: FibonacciSequenceGenerator):
      SequenceGenerator<Int>
}

Because you’re now delegating the creation of FibonacciSequenceGenerator to Dagger, you also need to change FibonacciSequenceGenerator.kt by adding the @Inject annotation, like so:

class FibonacciSequenceGenerator @Inject constructor() : SequenceGenerator<Int> {
  // ... 
}

Now, you can build again and check what’s in build/generated/source/kapt/debug, as shown in Figure 9.2:

Figure 9.2 — Generated code with @Binds in @Module
Figure 9.2 — Generated code with @Binds in @Module

As you can see, you now have just one file:

  • DaggerAppComponent.kt with 62 lines of code.

By using @Binds in place of @Provides, you reduced the number of files from two to one and the total number of lines of code from 102 to 62 — a reduction of about 40%! This has a great impact on your work: Fewer classes and lines of code mean faster building time.

@Binds was added in Dagger 2.12 specifically to improve performance. So you might wonder, why not always use @Binds? In theory, they’re the best choice, but:

  • In practice, you don’t always have an abstraction for a specific class.
  • An @Provides method can have multiple parameters of any type and cannot be abstract. A @Binds function must be abstract and can only have one parameter that must be a realization of its return type.
  • With an @Provides method, Dagger needs an instance of the @Module or it won’t be able to invoke it.
  • On the other hand, a @Provider can have some logic that decides which implementation to use based on some parameter values.

These are all aspects you need to consider before choosing the best option for you.

Providing existing objects

As you’ve learned, the @Component implementation is the Factory for the objects of the dependency graph. So far, you or Dagger had the responsibility to create the instance of a class bound to a specific type. But what happens if the object you want to provide already exists? A practical example can help.

Qureje 9.8 — Jza JuqwuqOpwFizbatajc juucq’q kilmiya
Gigixi 4.3 — Yba SicxevOpjDuqjilufs yuaqt’z disfiqo

Cogaqi 9.8 — QehRidiemka ad eqivuhiig
Dipili 9.8 — GudFefaekji ol ejeviciot

Using optional bindings

What happens if a binding isn’t present? So far, you get a compilation error, but sometimes you need to make a binding optional.

Using @BindsOptionalOf

Open SequenceViewBinderImpl.kt and change the property definition like this:

Using qualifiers

RaySequence contains two different model implementations of SequenceGenerator<T>:

Using the @Named annotation

The easiest way to solve the previous problem is by using the @Named annotation. To do that, just open AppModule.kt and add the following code:

Providing values for basic types

Providing a value for common types like Int or String with no qualifier can be dangerous. It would be easy to inject the wrong value, creating a bug that’s difficult to spot. Common types like Int or String are used to provide some configuration data. In that case encapsulation can help.

Using custom qualifiers

In the previous section, you learned how to bind different definitions to the same type using @Named. To do this, you had to define some String constants to use in your code. Dagger allows you to achieve the same goal in a more type-safe and less error-prone way by defining custom qualifiers.

Modules, bindings & Android Studio

While you were developing the example, you might have noticed some new icons in Android Studio, like the ones you see when you open AppModule.kt:

Pujeca 8.7 — Ozjlaad Wtocua hujwejn cev Ventol
Xusuqe 7.3 — Izkruov Sdulio zovyawr yub Najxev

Finding a type’s binding

You can find the source of a binding for a specific type by clicking the icon in Figure 9.6:

Hiwayu 2.2 — Culw Sadyiykh iwoq
Mamevu 5.2 — Gojc Hefgevyn efel

Zacebe 1.2 — Poirxa uf nqu bezgakk buranexein
Mivaki 3.2 — Ceowxu ox cri wajmexm cuzevixaum

Finding a type’s usage

In Figure 9.8, you see that the second icon allows you to find where Dagger injects that type.

Cifoqu 7.6 — Zupg ivtawkaem cec a dfzo
Qikege 5.2 — Nupt aksitwoem deb e pqte

Bunoda 5.5 — Hafihav suq u Fashoyidyw
Ragiho 6.5 — Zuvesoz guw e Sacqocuzmn

Key points

  • By using @Binds, Dagger generates fewer and shorter files, making the build more efficient.
  • Dagger @Modules allow you to provide existing objects, but you need to pass an instance of them to the @Component builder that Dagger generates for you.
  • @BindsOptionalOf allows you to have optional bindings.
  • You can provide different implementations for the same type by using @Named.
  • Custom qualifiers allow you to provide different implementations for the same type in a type-safe way.
  • From Android Studio 4.1, you can easily navigate the dependency graph from the editor.
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.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now