Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

13. Multibinding
Written by Massimo Carli

In the previous sections, you learned how Dagger works by migrating the Busso app from a homemade injection framework based on the ServiceLocator pattern to a fully scoped Dagger app. Great job!

Now, it’s time to use what you’ve learned to add a new feature to the Busso App, which will display messages from different endpoints at the top of the BusStop screen. For instance, you can display information about the weather and any traffic problems at your destination.

You also want to let the user add or remove new endpoints in a simple and declarative way. To do this, you’ll implement a small framework called an information plugin framework. Its high-level architecture looks like this:

Figure 13.1 — Information from a server
Figure 13.1 — Information from a server

Busso connects to different endpoints, gets some information and displays the messages at the top of the BusStop screen. This is a very simple use case that allows you to learn what Dagger multibinding is.

In this chapter’s starter project, you’ll find a version of Busso that already contains the first implementation of the information plugin framework.

In this chapter, you’ll examine the existing code, learning:

  • What multibinding is.
  • How to use multibinding with Set.

Multibinding is a very interesting Dagger feature because it simplifies how you integrate new features by using a plugin pattern, which you’ll learn all about in this chapter.

The information plugin framework

As you read in the introduction, Busso already contains a small framework that lets you fetch information to display at the top of the BusStop Fragment.

You can already see this very simple feature at work. Right now, it displays the coordinates of your current location:

Figure 13.2 — The WhereAmI information
Figure 13.2 — The WhereAmI information

Note: If the architecture of the information plugin framework is already clear to you, just skip to the Introducing Dagger Multibinding section ahead.

When you open the Busso project with Android Studio, you’ll see the source directory structure in Figure 13.3:

Figure 13.3 — The initial source directory structure
Figure 13.3 — The initial source directory structure

In particular, you’ll see a new plugins package. Here are its sub-packages and what they contain:

  • api: The main abstraction of the framework.
  • di: Dagger definitions.
  • impl: Implementation of the main abstraction.
  • model: A very simple class that models the information you’ll receive from the server.
  • ui: The presenter and viewbinder of the framework.
  • whereami: The classes you’ll need to implement the feature that displays the coordinates of your current location, as shown in Figure 13.3. This is the first feature that uses the information plugin framework.

An in-depth description of all the code would take too much space and time, so in this chapter, you’ll focus on the aspects related to dependency injection and, of course, Dagger.

Dagger configuration

The main aspect of this framework is the Dagger configuration you find in the plugins.di package. Open InformationPluginModule.kt in plugins.di and look at its code:

interface InformationPluginModule { // 1

  @Module
  interface ApplicationBindings { // 1
    @Binds
    fun bindInformationPluginRegistry(
        impl: InformationPluginRegistryImpl // 2
    ): InformationPluginRegistry
  }

  @Module
  interface FragmentBindings { // 1
    @Binds
    fun bindInformationPluginPresenter(
        impl: InformationPluginPresenterImpl // 3
    ): InformationPluginPresenter

    @Binds
    fun bindInformationPluginViewBinder(
        impl: InformationPluginViewBinderImpl // 3
    ): InformationPluginViewBinder
  }
}

This is the code of a @Module that contains all the bindings for the information plugin framework. These definitions give you insight into many important aspects of the framework. In particular:

  1. You define the InformationPluginModule using a simple interface that encapsulates all the bindings in a single place. In particular, ApplicationBindings acts as the @Module for the binding with @ApplicationScope. Similarly, FragmentBindings is the @Module for the objects with @FragmentScope.
  2. The framework has just one object that has @ApplicationScope: The InformationPluginRegistry, which contains the definitions for the plugins you want to use in the app. As you’ll see, this will need a way to describe the plugin to the framework and to register it.
  3. The bindings with @FragmentScope are related to presenter and view binder, which you can see directly in the source code in the project.

If you’re wondering why you need an InformationPluginRegistry at all, you’ll find out next.

InformationPluginRegistry

When it comes to working with plugins, frameworks, including the information plugin framework, have some important characteristics in common. They all need to:

  1. Describe the plugin to the framework.
  2. Register the plugin.

That’s why you need an abstraction to handle the registry responsibilities. In your case, this is InformationPluginRegistry, whose contents you can see in InformationPluginRegistry.kt in plugins.api:

interface InformationPluginRegistry {

  fun register(spec: InformationPluginSpec) // 1

  fun plugins(): List<InformationPluginSpec> // 2
}

A registry allows you to:

  1. Register the description of a plugin.
  2. Access the collection of existing plugins.

In the same interface, you can also see that you describe each plugin instance using an InformationPluginSpec. Open InformationPluginSpec.kt in plugins.api and look at its code:

interface InformationPluginSpec {

  val informationEndpoint: InformationEndpoint // 1

  val serviceName: String // 2
}

An InformationPluginSpec describes a specific information plugin in terms of:

  1. The endpoint to invoke.
  2. A name.

Now, look at the current implementation of InformationPluginRegistry by opening InformationPluginRegistryImpl.kt in plugins.impl and looking at the code:

@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor() : InformationPluginRegistry {

  private val plugins = mutableListOf<InformationPluginSpec>()

  override fun register(spec: InformationPluginSpec) {
    plugins.add(spec)
  }

  override fun plugins(): List<InformationPluginSpec> = plugins
}

This is a very simple implementation that stores the InformationPluginSpec instances you register in a simple MutableList<InformationPluginSpec>.

Next, you’ll look at how Busso uses InformationPluginSpec and InformationPluginRegistry.

The WhereAmI information plugin

To implement an information plugin, you need to follow these steps:

  1. Create an endpoint.
  2. Define a @Module to tell Dagger how to create the endpoint.
  3. Create an InformationPluginSpec for the information plugin.
  4. Add the @Module to ApplicationComponent, enhancing its dependency graph.
  5. Register the InformationPluginSpec to the InformationPluginRegistry.
  6. Build and run the app.

Look in the starter project in the material for this chapter to find the WhereAmI information plugin and you’ll see that it has each of these steps already completed for you. Here’s a closer look at how they work for the WhereAmI feature.

Creating an endpoint

Assuming that you have an actual endpoint to call, your first step is to define an implementation of InformationEndpoint, which you find in plugins.api.

Open InformationEndpoint.kt in plugins.api and you’ll see that InformationEndpoint is a very simple interface with a single operation. It allows you to fetch some information given a GeoLocation, which is simply a model with the longitude and latitude properties you already used for the bus stops.

interface InformationEndpoint {

  fun fetchInformation(location: GeoLocation): Single<InfoMessage>
}

The specific InformationEndpoint for the WhereAmI information plugin is in WhereAmIEndpoint.kt in plugins.whereami.endpoint:

interface WhereAmIEndpoint : InformationEndpoint

Find its implementation in WhereAmIEndpointImpl.kt in plugins.whereami.endpoint. Open it and you’ll see it contains the following code:

class WhereAmIEndpointImpl @Inject constructor(
    private val myLocationEndpoint: MyLocationEndpoint // 1
) : WhereAmIEndpoint {
  override fun fetchInformation(location: GeoLocation): Single<InfoMessage> =
      myLocationEndpoint.whereAmIInformation(location.latitude, location.longitude) // 2
}

In this class, you:

  1. Receive MyLocationEndpoint as the primary constructor parameter.
  2. Delegate MyLocationEndpoint to execute fetchInformation().

But what’s MyLocationEndpoint? Opening MyLocationEndpoint.kt in plugins.whereami.endpoint shows you that it’s simply the interface you define using Retrofit:

interface MyLocationEndpoint {
  @GET("${BUSSO_SERVER_BASE_URL}myLocation/{lat}/{lng}")
  fun whereAmIInformation(
      @Path("lat") latitude: Double,
      @Path("lng") longitude: Double
  ): Single<InfoMessage>
}

Note: The WhereAmI information plugin uses an existing endpoint in the Busso Server on Heroku. It simply returns a formatted string for the location that the server receives as a @Path of a GET request.

The UML diagram in Figure 13.4 gives you a better idea of the relationship between the different endpoint abstractions:

Figure 13.4 — The InformationEndpoint diagram
Figure 13.4 — The InformationEndpoint diagram

Your next step is to create the @Module.

Defining a @Module and creating InformationPluginSpec

Now, you need to tell Dagger how to create the objects it needs for WhereAmI. Open WhereAmIModule.kt in plugins.whereami.di and look at the code:

@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {

  @Provides
  @ApplicationScope // 1
  fun provideMyLocationEndpoint(retrofit: Retrofit): MyLocationEndpoint {
    return retrofit.create(MyLocationEndpoint::class.java)
  }

  @Provides
  @ApplicationScope // 2
  fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl): InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
    override val serviceName: String
      get() = "WhereAmI"
  }

  @Module
  interface Bindings { // 3

    @Binds
    fun bindWhereAmIEndpoint(
        impl: WhereAmIEndpointImpl
    ): WhereAmIEndpoint
  }
}

This code should be familiar to you. Here, you basically:

  1. Provide the MyLocationEndpoint implementation using Retrofit.
  2. Create and provide InformationPluginSpec for the WhereAmI plugin.
  3. Bind WhereAmIEndpoint to WhereAmIEndpointImpl.

The most important thing here is that you create InformationPluginSpec for the WhereAmI plugin, which completes the third step in the previous TODO list.

Now, you can also see how WhereAmIModule is one of the modules that ApplicationComponent uses for the bindings. Inside ApplicationComponent.kt in di, you’ll see:

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

Now, it’s finally time to register the InformationPluginSpec to the InformationPluginRegistry.

Registering InformationPluginSpec

This is the last and most important step in the process. Once you define InformationPluginSpec, you need to register it to InformationPluginRegistry to make it available to the framework. At the moment, you do this in Main.kt, which looks like this:

class Main : Application() {

  lateinit var appComponent: ApplicationComponent

  @Inject
  lateinit var informationPluginRegistry: InformationPluginRegistry // 1

  @Inject
  lateinit var whereAmISpec: InformationPluginSpec // 2

  override fun onCreate() {
    super.onCreate()
    // 3
    appComponent = DaggerApplicationComponent
        .factory()
        .create(this).apply {
          inject(this@Main) // 3
        }
    informationPluginRegistry.register(whereAmISpec) // 4
  }
}

In this code, you:

  1. Define informationPluginRegistry, which contains the reference to InformationPluginRegistry.
  2. Add whereAmISpec, which contains the reference to InformationPluginSpec for the plugin.
  3. Invoke inject() on ApplicationComponent for the injection. This, of course, requires that you define inject() with a parameter of type Main in ApplicationComponent.
  4. Register whereAmISpec to InformationPluginRegistry, invoking register().

Now you can finally build and run, getting what’s in Figure 13.5:

Figure 13.5 — The WhereAmI information
Figure 13.5 — The WhereAmI information

This is cool, but can you make the process easier and more declarative? That’s where Dagger’s multibinding feature comes in.

Introducing Dagger multibinding

To understand how multibinding helps implement the information plugin framework, take a moment to go over what you’ve done so far. You basically defined:

  1. The main abstractions that the different information plugins need to implement to be able to integrate into the framework.
  2. How to describe an information plugin to the framework.
  3. A registry containing all the information plugin definitions.

Every time you implement a location plugin, you need to:

  1. Describe the plugin using an InformationPluginSpec.
  2. Register the plugin to the framework.

In the current project, you explicitly did this in Main.kt by:

  1. Creating an instance of the InformationPluginSpec implementation.
  2. Registering that InformationPluginSpec implementation instance to the InformationPluginRegistry and invoking register().

But is all this scaffolding really necessary? The answer is, obviously, no.

Dagger multibinding solves this problem very simply, by letting you create a Set or a Map from some Dagger bindings in a declarative and pluggable way.

To see multibindings in action, you’ll migrate the information plugin framework to use them.

Using multibinding with Set

Your first step is to use Dagger multibinding with Set to implement the information plugin registration mechanism. This allows you to:

  1. Adapt the InformationPluginRegistry to use Set<InformationPluginSpec>.
  2. Dynamically and declaratively add InformationPluginSpec to Set<InformationPluginSpec> from Dagger binding definitions.

That’s all! In your next step, you’ll make a small refactor that will have a big impact.

Refactoring InformationPluginRegistry

You want all the InformationPluginSpecs you register to be in a Set<LocationPluginInfo>, so you need to change InformationPluginRegistry accordingly. To do this, open InformationPluginRegistryImpl.kt in plugins.impl and apply the following changes:

@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
    private val informationPlugins: Set<InformationPluginSpec> // 1
) : InformationPluginRegistry {

  // 2
  override fun plugins(): List<InformationPluginSpec> = informationPlugins.toList() // 3
}

In this code, you:

  1. Add informationPlugins as the primary constructor parameter with a type of Set<InformationPluginSpec>. This means that Dagger will inject a proper value when it resolves the bindings.
  2. You no longer need register() because you’re asking Dagger to register the plugins for you. Dagger will do this by putting your plugin spec into Set<InformationPluginSpec> directly. For the same reason, you don’t need plugins anymore, so you delete them both.
  3. Return informationPlugins from plugins() as a List<InformationPluginSpec>.

The second point means you should also delete register() from the interface. Open InformationPluginRegistry.kt in plugins.api and change it to the following:

interface InformationPluginRegistry {
  
  fun plugins(): List<InformationPluginSpec>
}

Now, InformationPluginRegistry uses Set<InformationPluginSpec>, which you provide through Dagger.

Next, it’s time to register the WhereIAm plugin.

Registering WhereIAm

This is the most interesting part: How can you register your plugin declaratively? Open WhereAmIModule.kt in plugins.whereami.di and you’ll see that you’re already telling Dagger how get an InformationPluginSpec for the WhereAmI plugin:

@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
  // ...
  @Provides
  @ApplicationScope
  fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl): InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
    override val serviceName: String
      get() = "WhereAmI"

  }
  // ...
}

What you’re not doing is telling Dagger to put that object into a Set<InformationPluginSpec>. Changing this is as simple as adding @IntoSet to the previous @Provides declaration, as in the following code:

@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
  // ...
  @Provides // 1
  @ApplicationScope // 2
  @IntoSet // 3 HERE
  fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl): InformationPluginSpec = object : InformationPluginSpec { // 4
    override val informationEndpoint: InformationEndpoint 
      get() = endpoint
    override val serviceName: String
      get() = "WhereAmI"

  }
  // ...
}

You won’t believe it, but this is all you have to do to configure multibinding.

Note: Spoiler alert! This isn’t completely true. :] There’s one thing you need to fix first, and you’ll see what that is soon.

With this code, you use:

  1. @Provides to provide the implementation of InformationPluginSpec that you want to put into Set<InformationPluginSpec>.
  2. @ApplicationScope to tell Dagger that you want to bind just one instance of InformationPluginSpec to the ApplicationComponent lifecycle.
  3. @IntoSet to tell Dagger that you want Set<InformationPluginSpec> to be available in the dependency graph, and that the instance you’re providing should be one of the objects in that graph.
  4. Finally, you return an object of type InformationPluginSpec. You’re not returning a Set<InformationPluginSpec>, but just one of the elements you want.

Before building and running the app, you need to clean up a few things.

Cleaning up your code

Open Main.kt and remove the excess code so it looks like this:

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

Here, you removed the:

  1. informationPluginRegistry property of type InformationPluginRegistry.
  2. inject() invocation on ApplicationComponent.
  3. register() invocation on informationPluginRegistry.

Of course, you don’t need inject() in the Main parameter of ApplicationComponent anymore because you don’t need to inject anything into Main.

Now, you can build and run. Oops! Something went wrong.

Using @JvmSuppressWildcards

When you build now, you get the following error:

ApplicationComponent.java:8: error: [Dagger/MissingBinding]
java.util.Set<? extends com.raywenderlich.android.busso.plugins.api
.InformationPluginSpec> cannot be provided without an @Provides-annotated method.

What’s going on? It looks like Dagger can’t find the object to inject into InformationPluginRegistry.

But Dagger should provide a Set<InformationPluginSpec> because of the following declaration in WhereAmI.kt in plugins.whereami.di:

  @Provides
  @ApplicationScope
  @IntoSet
  fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl): InformationPluginSpec = object : InformationPluginSpec { 
    override val informationEndpoint: InformationEndpoint 
      get() = endpoint
    override val serviceName: String
      get() = "WhereAmI"

  }

If you look carefully at the error message, however, you see that Dagger is looking for something other than Set<InformationPluginSpec>. It’s looking for a binding with a type of Set<? extends InformationPluginSpec>.

In binding declarations, types are very important to Dagger. The IS-A relationship doesn’t work unless it’s explicit. That means that if you define a binding for the type MyType, Dagger will look for an object of type MyType and not for a type that IS-A MyType.

You’ve seen this many times. If you define a binding for a type InformationPluginRegistry, Dagger won’t use an implementation like InformationPluginRegistryImpl unless you explicitly define it using @Binds or @Provides.

But why is Dagger looking for Set<? extends InformationPluginSpec> when you specified Set<InformationPluginSpec> as InformationPluginRegistryImpl’s primary constructor parameter type? Well, that’s Kotlin’s fault. :]

Kotlin converts any generic type into a Java generic with wildcards. That turns Set<InformationPluginSpec> into Set<? extends InformationPluginSpec> because Set<A> is covariant. If it would be contra-variant, a type MyType<in A> would become MyType<? super A>.

Note: Variance is a fundamental concept in Kotlin and other languages. If you want to learn more, read the book, Kotlin Apprentice.

Kotlin is the problem here, but it also offers a solution: @JvmSuppressWildcards. Using this annotation, you can tell Kotlin to ignore the wildcards and, in this case, to consider Set<InformationPluginSpec> to be, ahem, Set<InformationPluginSpec>. :]

Implementing @JvmSuppressWildcard

To implement this, open InformationPluginRegistryImpl.kt in plugins.impl and apply this change:

@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
    private val informationPlugins: @JvmSuppressWildcards Set<InformationPluginSpec> // HERE
) : InformationPluginRegistry {

  override fun plugins(): List<InformationPluginSpec> = informationPlugins.toList()
}

You could also use this as the annotation for the specific generic type parameter, if you needed to control each type parameter independently, like this:

@ApplicationScope
class InformationPluginRegistryImpl @Inject constructor(
    private val informationPlugins: Set<@JvmSuppressWildcards InformationPluginSpec> // HERE
) : InformationPluginRegistry {

  override fun plugins(): List<InformationPluginSpec> = informationPlugins.toList()
}

Now, you can finally build and run the app successfully. Just like before, you’ll see something like this:

Figure 13.6 — The WhereIAm location plugin feature
Figure 13.6 — The WhereIAm location plugin feature

Congratulations! You implemented the Location Plugin Framework using multibinding. Just by using @IntoSet, you’ve made an object available to a registry without any specific registry() invocation. Everything’s now declarative.

Adding a new information service plugin

As you learned in the first chapter of this book, it’s not important how fast you implement a feature but how fast you can change or extend it. To appreciate Dagger’s multibinding capabilities, you’re now going to add a new information plugin.

In this example, you’ll use a simple service from the Busso Server that lets you print a random weather condition message. At this point, it’s a simple text, but of course, you can extend the feature as you want.

As you learned earlier, you need to:

  1. Define an endpoint for the weather information feature.
  2. Define a @Module for the new information plugin that exposes InformationEndpoint and InformationPluginSpec.
  3. Add the @Module to the ones in ApplicationComponent.
  4. Build, run and enjoy your app.

It’s time to write some code.

Defining an endpoint

Create a new package, plugins.weather.endpoint, then add a new file named WeatherEndpoint.kt to it with the following code:

interface WeatherEndpoint {

  @GET("${BUSSO_SERVER_BASE_URL}weather/{lat}/{lng}")
  fun fetchWeatherCondition(
      @Path("lat") latitude: Double,
      @Path("lng") longitude: Double
  ): Single<InfoMessage>
}

Next, you need an implementation of WeatherInformationEndpoint.

Create a new file named WeatherInformationEndpoint.kt in plugins.wether.endpoint with the following code:

interface WeatherInformationEndpoint : InformationEndpoint

Now, create a new file named WeatherInformationEndpointImpl.kt in the same package and enter the following code:

class WeatherInformationEndpointImpl @Inject constructor(
    private val weatherEndpoint: WeatherEndpoint
) : WeatherInformationEndpoint {
  override fun fetchInformation(location: GeoLocation): Single<InfoMessage> =
      weatherEndpoint.fetchWeatherCondition(location.latitude, location.longitude)
}

Your next step is to create the @Module.

Creating the weather information @Module

Now, you need to create a @Module to tell Dagger about the InformationEndpoint you just created. Create a new package, plugins.weather.di, add a new file named WeatherModule.kt, then give it this code:

@Module(includes = [WeatherModule.Bindings::class])
object WeatherModule {

  @Provides
  @ApplicationScope
  fun provideWeatherEndpoint(retrofit: Retrofit): WeatherEndpoint {
    return retrofit.create(WeatherEndpoint::class.java)
  }

  @Provides
  @IntoSet // HERE
  @ApplicationScope
  fun provideWeatherSpec(endpoint: WeatherInformationEndpoint): InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
    override val serviceName: String
      get() = "Weather"

  }

  @Module
  interface Bindings {

    @Binds
    fun bindWeatherInformationEndpoint(
        impl: WeatherInformationEndpointImpl
    ): WeatherInformationEndpoint
  }
}

This follows the same structure as the WhereAmIModule you saw for the WhereAmI information plugin. The important part is that you use @IntoSet for provideWeatherSpec().

Now, you need to make the plugin available to the framework.

Adding a weather plugin

To add the WeatherModule to the modules in ApplicationComponent, open ApplicationComponent.kt in di and apply the following change:

@Component(modules = [
  ApplicationModule::class,
  InformationPluginModule.ApplicationBindings::class,
  WhereAmIModule::class,
  WeatherModule::class // HERE
])
@ApplicationScope
interface ApplicationComponent {
  // ...
}

Great, you’re ready to use the new plugin.

Checking your work

Now, build and run your app and you’ll see something like Figure 13.7:

Figure 13.7 — The Weather location plugin feature
Figure 13.7 — The Weather location plugin feature

This shows the top part of the screen only, to save space.

This is cool, right? But there’s a problem… in addition to the stormy weather here in London now. If you launch the app another time or you force BusStopFragment to reload, you might see the following output:

Figure 13.8 — The Weather location plugin feature
Figure 13.8 — The Weather location plugin feature

What’s the problem? In Figure 13.7, the WhereAmI information displays after the Weather information but in Figure 13.8, the location information is above the weather. That’s because a Set doesn’t contain duplicates, but it also doesn’t have any set order.

Of course, you could add a property to InformationPluginSpec and use it to sort how the data in the output displays.

However, this wouldn’t actually be simple, because all the requests to the different endpoints are asynchronous. Just because you sort how you send the requests doesn’t mean you’ll receive the responses in the same order.

Adding specific information to the InformationPluginSpec to fix a problem of the framework isn’t a good choice in terms of encapsulation and separation of concerns, either. As you’ll see later, Dagger multibinding allows you to add some information to the actual binding using multibinding with Map and a custom key.

You can use what you’ll learn in the following paragraphs to solve the ordering problem as an exercise.

More about Multibinding with Set: @ElementsIntoSet

In the previous example, you learned how to use @IntoSet to add a specific binding to a Set that Dagger creates for you and makes available to the dependency graph of the app.

It’s worth mentioning that the Set Dagger creates is immutable. Dagger defines the content of the Set when it creates the dependency graph and you can’t change it later. But nobody’s preventing you from using a Set<Provider<A>> or Set<Lazy<A>> if you don’t want to create everything when the app starts.

In the previous examples, you created a different definition for each InformationPluginSpec. In this case, each information plugin had to add this definition to the related @Module.

You have another option, though: defining all the InformationPluginSpec in the same place. Try this by creating a new file named InformationSpecsModule.kt in plugins.di and add the following code:

@Module(
    includes = [
      WhereAmIModule::class,
      WeatherModule::class
    ]
)
object InformationSpecsModule {

  @Provides
  @ElementsIntoSet // 1
  @ApplicationScope
  fun provideWeatherSpec(
      @Named(WHEREAMI_INFO_NAME) whereAmISpec: InformationPluginSpec, // 2
      @Named(WEATHER_INFO_NAME) weatherSpec: InformationPluginSpec // 2
  ): Set<InformationPluginSpec> { // 3
    return mutableSetOf<InformationPluginSpec>().apply {
      add(whereAmISpec)
      add(weatherSpec) 
    }
  }
}

In this code, you can see that you use:

  1. @ElementsIntoSet to add more than one element to the Set<InformationPluginSpec> at once.
  2. @Named(WHEREAMI_INFO_NAME) and @Named(WEATHER_INFO_NAME) to identify the two different implementations of InformationPluginSpec. Without @Named, Dagger would be confused about which to use.
  3. Set<InformationPluginSpec> as the return value’s type.

Of course, you need to update the definition in WhereAmIModule.kt, like this:

const val WHEREAMI_INFO_NAME = "WhereAmI" // 1

@Module(includes = [WhereAmIModule.Bindings::class])
object WhereAmIModule {
  // ...
  @Provides
  @ApplicationScope
  // 2
  @Named(WHEREAMI_INFO_NAME) // 3
  fun provideWhereAmISpec(endpoint: WhereAmIEndpointImpl): InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
    override val serviceName: String
      get() = WHEREAMI_INFO_NAME 
  }
}

Note that you:

  1. Have a WHEREAMI_INFO_NAME constant for the name of the plugin.
  2. Removed @IntoSet.
  3. Added @Named(WHEREAMI_INFO_NAME) as a qualifier of the binding.

You need to do the same in WeatherModule.kt:

const val WEATHER_INFO_NAME = "Weather"

@Module(includes = [WeatherModule.Bindings::class])
object WeatherModule {
  // ...
  @Provides
  @Named(WEATHER_INFO_NAME)
  @ApplicationScope
  fun provideWeatherSpec(endpoint: WeatherInformationEndpoint):
      InformationPluginSpec = object : InformationPluginSpec {
    override val informationEndpoint: InformationEndpoint
      get() = endpoint
    override val serviceName: String
      get() = WEATHER_INFO_NAME
  }
}

For your last step, you need to update ApplicationComponent.kt to include InformationSpecsModule::class and to remove WhereAmIModule::class and WeatherModule::class.

Here’s the result:

@Component(modules = [
  ApplicationModule::class,
  InformationPluginModule.ApplicationBindings::class,
  InformationSpecsModule::class
])
@ApplicationScope
interface ApplicationComponent {

  fun activityComponentBuilder(): ActivityComponent.Builder

  @Component.Factory
  interface Builder {

    fun create(@BindsInstance application: Application): ApplicationComponent
  }
}

Now, just build and run and check that everything works as expected.

Figure 13.9 — The information plugins in the Busso app
Figure 13.9 — The information plugins in the Busso app

@ElementsIntoSet is useful when you need to provide a set of objects as part of, for instance, an initial setup for the framework or a set of predefined objects.

Wow! This has been a very dense chapter. You learned a lot about multibinding and you managed to implement a simple framework that allows you to integrate new services in Busso in a simple and declarative way. Multibinding uses more than just Set.

In the next chapter, you’ll learn everything you need to know about multibinding with Map.

Key points

  • Dagger multibinding allows you to add functionality to your app in an easy and declarative way.
  • You can use multibinding with both Set and Map.
  • @IntoSet allows you to populate a Set when you initialize the dependency graph.
  • Types are fundamental to Dagger. Use @JvmSuppressWildcards to fix a variance problem that occurs when Dagger resolves the objects to inject.
  • @ElementsIntoSet allows you to put more than one object into a multibinding Set.
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.