Home · Android & Kotlin Tutorials

Dagger 2 Tutorial for Android: Advanced – Part 2

In this tutorial, you’ll learn how to implement advanced features of Dagger 2 by using subcomponents, custom scopes and multibinding.

5/5 2 Ratings

Version

  • Kotlin 1.3, Android 4.1, Android Studio 3.5

As you saw in part one of our Dagger 2 Tutorial For Android: Advanced, Dagger is one of the most-used dependency injection (DI) libraries in Android. It helps you generate code to provide the dependencies for your app. By using it correctly and combining it with your architecture setup, you make your dependency injection clear and distinct – without a lot of work! :]

In the previous tutorial, you tried the main concepts and use cases of Dagger with @Inject, @Module and @Component. You used @Binds and experimented with methods of creating @Component instances using @Component.Builder and @Component.Factory.

Finally, you learned about @Singleton and its relationship with @Components that use it.

As your app grows in size and complexity, you might add multiple @Components. You might also need to separate code into smaller logical groups to reuse across the project by creating different Android Studio modules.

For these cases, Dagger offers powerful tools like @Subcomponent and an interesting feature called multibindings, which allows you to bind the same type, in a map or set of dependencies of that type. This way you can pick and choose which concrete implementation you need. On top of that, performance is always important, so it’s helpful to use different @Scopes.

In this tutorial you’ll:

  • Use @Singleton to define a custom @Scope.
  • Learn how to bind custom @Scopes to dependent @Component instances.
  • Manage dependencies between different @Components through their dependencies attribute.
  • Learn what @Subcomponenta are and how they manage dependencies between @Components with different @Scopes.
  • Find out what multibinding is and how you can use it in your apps.
Note: This tutorial assumes you’re familiar with Android development and Android Studio. If these topics are new to you, read the Beginning Android Development and Kotlin for Android tutorials first.

It also presumes you have knowledge of Dagger 2. If not, check out our Dependency Injection in Android with Dagger 2 and Kotlin and Dagger 2 Tutorial For Android: Advanced – Part One tutorials.

Getting Started

Download and unzip the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open the project using Android Studio 3.5 or greater, then build and run it. You’ll see this:

RwNews App

RwNews App

In this tutorial, you’ll work on RwNews, a basic app that displays a list of news items. The user can select any news item to read its content.

The app is very simple, but its functionality is not as important as its internal workings.

Look at the project structure in Android Studio. You’ll notice a classic MVP architectural pattern implementation with definitions as in the following UML class diagram:

RwNews Architecture

RwNews Architecture

This is the same app from the previous Dagger 2 Tutorial For Android: Advanced tutorial.

Open InitApp.kt and you’ll see the following:

class InitApp : Application() {
  
  lateinit var appComponent: AppComponent

  override fun onCreate() {
    super.onCreate()
    appComponent = DaggerAppComponent
      .factory()
      .repository(MemoryNewsRepository()) // HERE
  }

  fun appComp() = appComponent
} 

Here, you explicitly create a single instance of MemoryNewsRepository. You pass its reference to AppComponent, then share a single instance between all the different Fragment implementations.

But Dagger allows you to get the same result in a better way, without hard-coding dependency creation.

Managing Multiple @Scopes

Dagger doesn’t just generate code, it also allows you to manage dependencies and their lifecycle within the components you define. To do that, it needs to understand how different objects connect and how to provide a way to build the related graph at runtime.

Creating the MemoryNewsRepository instance should be Dagger’s responsibility, as well as creating NewsListPresenterImpl and NewsDetailPresenterImpl.

All objects need memory when you create them, but you don’t always need to create all of the objects at the same time. Your app always needs an instance of MemoryNewsRepository, but you only need an instance of NewsDetailPresenterImpl when you open NewsDetailFragment.

As such, different objects have different lifecycles. This is what @Scope represents.

Exploring @Singleton

Look at the source code for @Singleton:

// 1
package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Scope // 2
@Documented // 3
@Retention(RUNTIME) // 4
public @interface Singleton {} // 5

Note that:

  1. It’s not a Dagger definition. The package javax.inject comes from a the JSR-330 which is the Java Specification Request about annotations.
  2. @Scope allows you to tag this definition as a scope. @Scope is an example of a tagging annotation and is part of the same javax.inject package.
  3. Annotations are usually not included in JavaDocs. @Documented allows you to inform tools that this should be part of the JavaDoc.
  4. @Retention is very important. It informs the compiler of the level of retention or persistence of the annotation during the building process. RUNTIME indicates that the annotation persists up to the execution of the code you annotated.
  5. The @Singleton doesn’t have any attributes, which is common for most scopes.

As you can see in the previous definition, the @Singleton is just a scope-tagged annotation. It’s used to tag other annotations or classes, to add extra scoping behavior.

Using @Singleton

The previous tutorial demonstrated, that @Singleton‘s power transfers to the @Component that uses it. When you define a @Component with a lifecycle of the Application object and annotate it with @Singleton, all the objects within the same dependency graph with that annotation will have the same lifecycle.

To make your app rely on @Singleton, open AppComponent.kt and remove @Component.Factory. Then add @Singleton, ending up with the following implementation:

@Component(modules = [AppModule::class])
@Singleton // HERE
interface AppComponent {

  fun inject(frag: NewsListFragment)

  fun inject(frag: NewsDetailFragment)
}

Now, change the content of the MemoryNewsRepository.kt like so:

@Singleton // HERE
class MemoryNewsRepository @Inject constructor(): NewsRepository {
  - - -
}

Here, you bind the lifecycle of MemoryNewsRepository to the @Component that manages it, using @Singleton.

Then, add the following definition at the end of the AppModule.kt:

@Module
abstract class AppModule {
  - - - 
  @Binds
  abstract fun provideNewsRepository(newsRepository: MemoryNewsRepository): NewsRepository
}

This tells Dagger what implementation of the <code>NewsRepository</code> interface to use.

Finally, insert the Dagger-generated create() into AppInit like this:

class InitApp : Application() {

  lateinit var appComponent: AppComponent

  override fun onCreate() {
    super.onCreate()
    appComponent = DaggerAppComponent.create() // HERE
  }

  fun appComp() = appComponent
}

At this point, Dagger has all the information it needs to create an instance of MemoryNewsRepository. It can use it in every place you need a NewsRepository.

Build and run the app!

RwNews App

RwNews App

Verify you’re using the same instance of MemoryNewsRepository by using the string AdvDagger as a filter in LogCat and checking the output:

I/AdvDagger: In NewsListPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab
I/AdvDagger: In NewsDetailPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab
I/AdvDagger: In NewsListPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab
I/AdvDagger: In NewsDetailPresenterImpl using Repository .repository.impl.MemoryNewsRepository@e2aaaab

Using @Singleton both on the component and the dependency, you’ll reuse the same instance of the dependency as long as the component instance is alive. As long as you initialize the component only once, this will be true, and you’re doing that in InitApp. :]

Creating a Feature @Component

The NewsRepository implementation must be a single instance shared between all the objects of the app. This isn’t true for NewsListPresenter and NewsDetailPresenter, which should be present only while you display the news. Those classes have a different lifecycle.

If the app had some different features, you’d want to release those instances once you’re done with the feature to avoid wasting memory. Doing this requires two things:

  1. A @Component whose lifecycle is bound to the feature.
  2. A custom @Scope to bind the dependencies to their related @Component.

Your first step is to set up the new @Component. Create a new file named FeatureComponent.kt in the di package like this:

@Component(modules = [FeatureModule::class])
interface FeatureComponent {

  fun inject(frag: NewsListFragment)

  fun inject(frag: NewsDetailFragment)
}

The FeatureComponent will hold the dependencies for the two features of the app – the news list, and details. This code is nearly the same as AppComponent‘s.

Next, change AppComponent to this:

@Component(modules =[AppModule::class])
@Singleton
interface AppComponent {
}

You removed the code that’s now in the FeatureComponent. Now, you need to move the contents of AppModule into a new file. Create FeatureModule.kt in the di package and add:

@Module
abstract class FeatureModule {

  @Binds
  abstract fun provideNewsListPresenter(newsRepository: NewsListPresenterImpl): NewsListPresenter

  @Binds
  abstract fun provideNewsDetailPresenter(newsRepository: NewsDetailPresenterImpl): NewsDetailPresenter
}

This contains the definition that previously was in AppModule.kt.

Now, you’ll change that definition to:

@Module
abstract class AppModule {

  @Binds
  abstract fun provideNewsRepository(newsRepository: MemoryNewsRepository): NewsRepository
}

This contains only the @Binds for the NewsRepository.

Try to build and run and you’ll get the following error:

FeatureComponent.java:7: error: [Dagger/MissingBinding] com.raywenderlich.rwnews.repository.NewsRepository cannot be provided without an @Provides-annotated method.
public abstract interface FeatureComponent {
...

This is because you haven’t told Dagger how to use the new @Component yet, and also because Dagger doesn’t know how to manage the NewsRepository implementation. That implementation is the responsibility of AppComponent.

For your next step, you’ll make FeatureComponent use objects from AppComponent.

Managing @Component Dependencies

As you’ve seen, building the app throws an error because FeatureComponent doesn’t know how to implement NewsRepository. You can fix this by using @Component‘s dependencies attribute.

Open and modify the FeatureComponent like this:

@Component(
  modules = [FeatureModule::class],
  dependencies = [AppComponent::class] // HERE
)
interface FeatureComponent {
 - - -
}

Here, you’re telling Dagger that the new FeatureComponent needs objects from the dependency graph that AppComponent manages. Build and run the app and you’ll get a different error:

FeatureComponent.java:6: error: com.raywenderlich.rwnews.di.FeatureComponent (unscoped) cannot depend on scoped components:
@dagger.Component(modules = {com.raywenderlich.rwnews.di.FeatureModule.class}, dependencies = {com.raywenderlich.rwnews.di.AppComponent.class})

Here, AppComponent is scoped because it uses @Singleton, but Dagger is complaining that you can only create dependencies between scoped components. That’s because Dagger doesn’t understand the relationship between objects in @FeatureComponent and the ones in @AppComponent.

To address this, you need a custom scope, which you’ll make next.

Creating Your Custom @Scope

Creating a custom @Scope is simple; it’s similar to @Singleton‘s code.

Create a file named FeatureScope.kt in the di package and add the following code:

import javax.inject.Scope

@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class FeatureScope

It’s like @Singleton with a different name and with @MustBeDocumented in place of the deprecated @Documented.

Now, add the following to FeatureComponent:

@Component(
  modules = [FeatureModule::class],
  dependencies = [AppComponent::class]
)
@FeatureScope // HERE
interface FeatureComponent {
 - - -
}

This implements the new @Scope.

Build and run and you’ll notice that the previous error has disappeared. However, Dagger’s now complaining about NewsRepository.

Dependencies Between Differently-Scoped @Components

You can fix the problem easily. If one @Component wants to use objects from another using the dependencies attribute, a function needs to explicitly expose them.

In this case, add the following definition to AppComponent:

@Component(modules = [AppModule::class])
@Singleton
interface AppComponent {

  fun repository(): NewsRepository // HERE
}

This function tells FeatureComponent how to access NewsRepository‘s implementation, even with a different scope.

Build and run. The app will work from Dagger’s side, but you still have to use @FeatureComponent instead of @AppComponent to inject dependencies.

Injecting With a Custom @Component

In the next step, you’ll tell Dagger when to use and release the new FeatureComponent. In this case, the lifecycle of the feature is the lifecycle of MainActivity.

You’ll create FeatureComponent in the MainActivity, just as you created AppComponent in InitApp.

You also need to pass AppComponent‘s reference to FeatureComponent to manage NewsRepository‘s dependencies.

Start by going to MainActivity.kt and add the following code:

import javax.inject.Provider
// 1
typealias FeatureComponentProvider = Provider<FeatureComponent>

// 2
class MainActivity : AppCompatActivity(), FeatureComponentProvider {
  // 3
  lateinit var featureComp: FeatureComponent

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      supportFragmentManager.beginTransaction()
        .replace(R.id.anchor, NewsListFragment())
        .commit()
      // 4
      val appComp = (applicationContext as InitApp).appComp()
      // 5
      featureComp = DaggerFeatureComponent.builder()
        .appComponent(appComp)
        .build()
    }
  }

  // 6
  override fun get(): FeatureComponent = featureComp
}

You’ve done many important things with this code:

  1. You defined FeatureComponentProvider as a typealias for Provider<FeatureComponent>. This will be useful when you need to access FeatureComponent from Fragments.
  2. MainActivity now implements the FeatureComponentProvider.
  3. You created lateinit property, which stores the reference to FeatureComponent.
  4. You need the reference to AppComponent. You get it from InitApp using casting.
  5. Dagger created the DaggerFeatureComponent for you with the Builder that defines appComponent()/code>. This lets you pass AppComponent's reference.
  6. The MainActivity implements the FeatureComponentProvider. You have to override get() to provide the FeatureComponent.

The last step is to use FeatureComponent in both NewsListFragment and NewsDetailFragment. This is very easy – you just have to replace the current implementation of onAttach() in both classes, like this:

override fun onAttach(context: Context) {
  (context as FeatureComponentProvider).get().inject(this)
  super.onAttach(context)
}

Now, build and run. The app will finally run as usual! :]

Functional RwNews App

RwNews App

You just connected a bunch of things, but... wait, you forgot something! You'll address that next.

Connecting the Custom @Scope

In the previous code, you used @FeatureScope only on the FeatureComponent – there are no objects with the same annotation.

This means that you're creating new instances of NewsListPresenterImpl and NewsDetailPresenterImpl at every injection.

Test that by adding this log message to NewsListFragment:

  override fun onAttach(context: Context) {
    (context as FeatureComponentProvider).get().inject(this)
    super.onAttach(context)
    Log.i(TAG, "In NewsListFragment using NewsListPresenter $newsListPresenter")
  }

Or add the following to NewsDetailFragment:

  override fun onAttach(context: Context) {
    (context as FeatureComponentProvider).get().inject(this)
    super.onAttach(context)
    Log.i(TAG, "In NewsDetailFragment using NewsDetailPresenter $newsDetailPresenter")
  }

Build and run the app. You'll see a log like this:

I/AdvDagger: In NewsListFragment using NewsListPresenter .presenter.impl.NewsListPresenterImpl@ccf8169
/I/AdvDagger: In NewsDetailFragment using NewsDetailPresenter .presenter.impl.NewsDetailPresenterImpl@e66466f // DIFFERENT
I/AdvDagger: In NewsDetailFragment using NewsDetailPresenter .presenter.impl.NewsDetailPresenterImpl@27a84f // DIFFERENT

Every time you display the details, Dagger creates a new instance of the NewsDetailPresenterImpl. That's not good, but the solution is very easy. Just annotate the NewsDetailPresenterImpl like this using your custom scope @FeatureScope:

@FeatureScope // HERE
class NewsDetailPresenterImpl @Inject constructor(
  private val newsRepository: NewsRepository
) : BasePresenter<NewsModel, NewsDetailView>(),
  NewsDetailPresenter {
  - - -
}

Build and run the app again, repeating the same actions. You'll see a log like this:

I/AdvDagger: In NewsListFragment using NewsListPresenter .presenter.impl.NewsListPresenterImpl@ccf8169
I/AdvDagger: In NewsDetailFragment using NewsDetailPresenter .presenter.impl.NewsDetailPresenterImpl@e66466f // SAME
I/AdvDagger: In NewsDetailFragment using NewsDetailPresenter .presenter.impl.NewsDetailPresenterImpl@e66466f // SAME

Now, NewsDetailPresenterImpl's instance has the same lifecycle as FeatureComponent, which is the same lifecycle as MainActivity.

Component Dependencies With @Subcomponent

Previously, you learned how to define different @Components with different @Scopes and how to make them collaborate.

To do this, you had to pass AppComponent's reference to FeatureComponent using Builder, which Dagger implemented for you.

This is a good solution for cases where AppComponent doesn't need to know about a FeatureComponent depending on it.

Dagger also allows a different approach: What if AppComponent is a factory for the dependent modules, and with that, the FeatureComponent?

In that case, AppComponent wouldn't need to expose NewsRepository explicitly because Dagger would connect everything for you. The code in the MainActivity would be much simpler.

Your next step will be to achieve this behavior using a @Subcomponent.

Migrating to @Subcomponent

Migrating to use @Subcomponents is easy. Open FeatureComponent.kt and replace @Component with @Subcomponent. Then remove dependencies like this:

@Subcomponent( // HERE
  modules = [FeatureModule::class]
)
@FeatureScope
interface FeatureComponent {
 - - -
}

Instead of FeatureComponent being a fully-fledged component, you'll switch to using a Subcomponent, which stems out of the AppComponent. Now, edit AppComponent like this:

// 1
@Component(modules = [AppModule::class, FeatureModule::class])
@Singleton
interface AppComponent {
  // 2
  fun featureComp(): FeatureComponent
}

After removing repository(), this code did the following:

  1. AppComponent needs to know how to build the dependency graph for FeatureComponent, so you added the reference to FeatureModule.
  2. Defined a factory method for FeatureComponent.

The last step is to change MainActivity to get the reference to FeatureComponent directly from AppComponent. You do that like this:

typealias FeatureComponentProvider = Provider<FeatureComponent>

class MainActivity : AppCompatActivity(), FeatureComponentProvider {

  lateinit var featureComp: FeatureComponent

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    if (savedInstanceState == null) {
      supportFragmentManager.beginTransaction()
        .replace(R.id.anchor, NewsListFragment())
        .commit()
      // HERE
      featureComp = (applicationContext as InitApp).appComp().featureComp()
    }
  }

  override fun get(): FeatureComponent = featureComp
}

Here, you get the reference to AppComponent as usual, then use featureComp() to get the reference to FeatureComponent.

Build and run once more, to make sure things haven't changed internally! :]

RwNews App still works

RwNews App

@Component Dependencies Versus @Subcomponent

So far, you've seen two ways of managing dependencies between graph components with different @Scopes. But is there a best approach? The answer is: It depends. :]

@Subcomponents allow you to write less code because you've exposed all the objects in the dependency graph, not just the ones you publish using specific functions.

On the other hand, each @Component needs to know what the @Subcomponents are, which can lead to problems with circular dependencies. For this reason, using the dependencies attribute of @Component is, at the moment, the only option for apps with multiple modules.

To understand this, consider the following dependency diagram between the module containing the NewsRepository implementation and the classes you use for displaying news.

Dependency cycle, with News depending on NewsRepository and vice-versa

Dependency Cycle

The feature displays news from NewsRepository, so there must be a dependency between them. The image shows this dependency with label 1.

When you define the @Subcomponent, you need to create its factory function in the main @Component. This introduces the dependency marked 2 in the diagram.

This, in turn, creates a cycle that prevents the usage of @Subcomponent between classes in different modules.

If you use the dependencies attribute of @Component, you don't need the dependency with label 2, but the main @Component must explicitly expose the objects that the dependent object can use.

This isn't a bad thing; it's a version of encapsulation. Another problem with this approach, however, is that you can't use multibindings, as you'll learn next.

Multibinding in Dagger

In your app, you can select news and display its details. Now, suppose you want to add a feature to print some stats on the content of the news.

First, create a stats package and create NewsStats.kt in the new package. Then model all of the stats types using the following abstraction:

const val STATS_LOG = "NEWS_STATS"

interface NewsStats {

  fun printStats(news: News)
}

This is a simple interface defining printStats(), which receives the NewsModel and prints some statistics.

The file also contains STATS_LOG, which you use for the specific statistic's output. A simple implementation could be the one that calculates the length of the news item.

Create LengthNewsStats.kt in the same package with the following code:

class LengthNewsStats : NewsStats {
  override fun printStats(news: News) {
    Log.i(STATS_LOG, "News Length: ${news.body.length}")
  }
}

But how do you use it? It gives you the ability to add different NewsStats implementations without changing the code.

Adding Different Implementations

Now, imagine you want to calculate different type of statistics when the app displays the news. To do so, edit the NewsDetailPresenterImpl.kt like so:

@FeatureScope
class NewsDetailPresenterImpl @Inject constructor(
  private val newsRepository: NewsRepository,
  // 1
  private val newsStats: @JvmSuppressWildcards(true) Set<NewsStats>
) : BasePresenter<NewsModel, NewsDetailView>(),
  NewsDetailPresenter {

  override fun displayNewsDetail(newsId: Long) {
    Log.i(TAG, "In NewsDetailPresenterImpl using Repository $newsRepository")
    newsRepository.byId(newsId)?.let { news ->
      view?.displayNews(news)
      // 2
      newsStats.forEach { stats ->
        stats.printStats(news)
      }
    }
  }
}

In the code above you:

  1. Added a new constructor parameter of type Set&ltNewsStats&gt with all the instances of NewsStats you want to use. To make Dagger work in Kotlin, you also need to use @JvmSuppressWildcards. This allows the Kotlin compiler to avoid generating wildcard code, and to focus on the concrete type instead.
  2. Iterated over NewsStats, invoking printStats() for the news you display.

Build and run the project, and an error. Dagger complains. It does that often, doesn't it? :]

It's throwing an error because it doesn't know how to create Set&ltNewsStats&gt. You already know how to solve this, though: Use a @Module. To keep things separate and because the existing FeatureModule is abstract, start by defining a new class and file called StatsModule.kt in the di package:

@Module
class StatsModule {

  @Provides
  fun provideNewsStats(): Set<NewsStats> = setOf(
    LengthNewsStats()
  )
}

Here, you simply create an object of type Set&ltNewsStats&gt and initialize it with an instance of LengthNewsStats. At the moment, that's the only implementation of NewsStats.

To use this definition, you also have to add a reference to @Module in AppComponent like so:

@Subcomponent(
  modules = [
    FeatureModule::class,
    StatsModule::class // HERE
  ]
)
@FeatureScope
interface FeatureComponent {
  - - -
}

Build and run, then open News Details. You'll notice that everything works as you expected.

RwNews App

RwNews App

Filter the LogCat output using NEWS_STATS I/NEWS_STATS: News Length: 1013

You successfully added one implementation of the stats utility class, to log the news! Now let's see about implementing another one.

Implementing Multibinding

This wasn't really multibinding – you just created another provider, like before! You defined a new dependency as usual, with an object of type Set&ltNewsStats&gt.

However, multibinding allows you to do more.

Suppose you want a new feature that defines and uses its own NewsStats. One option is to add an instance of the new class into the StatsModule. However, this would break the principle of separation of concern and make the code brittle, as only the new feature should care about providing the dependency.

To communicate the new NewsStats to Dagger, start off by creating a new package named thirdparty. Then create WordCountNewsStats.kt in the new thirdparty package, with the following code:

class WordCountNewsStats : NewsStats {
  override fun printStats(news: News) {
    val wordsCount = news.body.splitToSequence(" ").count()
    Log.i(STATS_LOG, "News Word count: $wordsCount")
  }
}

Getting Dagger to Use the Multibinding Implementation

Now you need to tell Dagger that you want to add this implementation to the Set&ltNewsStats&gt it provides.

You will do this by creating a new @Module implementation. Define a ThirdPartyStatsModule.kt in the same thirdparty package:

@Module
class ThirdPartyStatsModule {

  @Provides
  @IntoSet // HERE
  fun provideWordsCountNewsStats(): NewsStats = WordCountNewsStats()
}

Note that provideWordsCountNewsStats() doesn't return a Set&ltNewsStats&gt, but rather an object of type NewsStats. Using @IntoSet, you inform Dagger that you want to contain that instance in the Set&ltNewsStats&gt Dagger injects.

Now you need to add the ThirdPartyStatsModule to the FeatureComponent:

@Subcomponent(
  modules = [
    FeatureModule::class,
    StatsModule::class,
    ThirdPartyStatsModule::class // HERE
  ]
)
@FeatureScope
interface FeatureComponent {
 - - 
}

You added the ThirdPartyStatsModule to the component, so that you include the new NewsStats implementation in the dependency graph.

Build and run and you'll get another error. Dagger is complaining about a double binding for Set&ltNewsStats&gt.

That's because it doesn't know that the previous is a part of a multibinding definition. You can fix this using another important annotation in StatsModule:

@Module
class StatsModule {

  @Provides
  @ElementsIntoSet // HERE
  fun provideNewsStats(): Set<NewsStats> = setOf(
    LengthNewsStats()
  )
} 

Using @ElementsIntoSet, you're telling Dagger that the Set&ltNewsStats&gt you're providing is part of a multibinding that other modules can contribute to.

Now, build and run. Open News Details and check the LogCat that contains output like this:

I/NEWS_STATS: News Word count: 153 // HERE
I/NEWS_STATS: News Length: 1013
RwNews App

RwNews App

The new NewsStats is now part of Set&ltNewsStats&gt. All of the NewsStats implementations, which are built into the set, are part of the same set of the depdendency type. Then when you inject the set, you get all of the implementations of the same type, within the set!

Multibinding is a very powerful tool, but it only works with Sets and Maps. This causes extra work if you want to force the items to display in a specific order.

Congratulations! You've completed this tutorial and learned more about how to use Dagger. :]

Where to Go From Here?

Download the end project using the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you learned how to create a custom @Scope to manage dependency lifecycle and how to organize the code to manage dependencies between different @Components. You also had the chance to see how multibinding works!

You're a Dagger expert now, but the library evolves continuously. Google is working hard to resolve the dependency cycle problem with @Component and @Subcomponent dependencies. Stay tuned for updates on raywenderlich.com.

I hope you found this tutorial helpful! If you have any comments or questions, feel free to join in the forum discussion below.

Average Rating

5/5

Add a rating for this content

2 ratings

More like this

Contributors

Comments