Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

7. More About Injection
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 started using Dagger with a very basic Server-Repository example. As you remember from the first chapter, the code you implemented uses a simple dependency between the Server and a Repository called loosely coupled. You represent this dependency with the UML diagram in Figure 7.1:

Figure 7.1 — Loosely coupled dependency
Figure 7.1 — Loosely coupled dependency

You learned how to tell Dagger how to generate the factory for the instances in the dependency graph using the @Component annotation. You then learned how to use the @Inject annotation to accomplish two different goals:

  1. Tell Dagger what constructor to call to create an instance of a class.
  2. Mark properties as targets for injection.

If the type of dependency is an abstraction, like an interface, Dagger needs some additional information. You provide this information by using a @Module containing some functions that you annotate with @Provides. This way, you tell Dagger which function to invoke to get an instance of a class for a specific type. Luckily, Dagger is a good listener. :]

You learned that the @Inject, @Component, @Module and @Provides annotations are all you need to implement dependency injection in your app with Dagger. The rest of the annotations let you improve performance when generating and executing the code.

In this chapter, you’ll discover even more about dependency injection with Dagger. You’ll learn how to:

  • Deal with constructor, field and method injection with Dagger.
  • Simplify the implementation of @Module by using @Binds in cases when you have an abstraction and its implementation. You saw how this works in the Repository and FakeRepository example.
  • Use @Singleton for the first time to solve a very common problem.

There’s still a lot to do. Prepare to have some more fun!

Getting started

In the previous chapter, you learned how to use some Dagger annotations in a Kotlin project in IntelliJ. In this chapter, you’ll return to Android with the RaySequence app. This is a very simple app that allows you to display a numeric value of a sequence on the screen every time you press a Button.

To get started, use Android Studio to open the RaySequence project in the starter folder of the materials for this chapter. Build and run and you’ll get the screen shown in Figure 7.2:

Figure 7.2 — Initial RaySequence app
Figure 7.2 — Initial RaySequence app

Note: Don’t worry about the Busso App. In a few chapters, you’ll migrate it to Dagger and everything will seem very easy to you.

At the moment, the app doesn’t work: When you click the Button, nothing happens. Figure 7.3 shows the file structure of the app:

Figure 7.3 — RaySequence file structure
Figure 7.3 — RaySequence file structure

As you see, the app uses the same mvp library you saw in the previous chapters and the implementations for Model, ViewBinder and Presenter have already been done. However, you still need to connect the dots.

Before doing this, take a quick look at the model. Open SequenceGenerator.kt in the model package of the app module and look at the following code:

interface SequenceGenerator<T> {
    fun next(): T
}

SequenceGenerator<T> is a simple abstraction to let any object provide the next element of a sequence through its next() operation.

Note: The Kotlin standard library already provides the Sequence<T> interface, which is similar to SequenceGenerator<T> and has some utility builders like the sequence() higher-order function. However, using Sequence<T> requires you to define an Interator<T>, which makes the code a bit more complex with no gain in the context of dependency injection.

In the same model package are two SequenceGenerator<T> implementations. NaturalSequenceGenerator.kt contains a simple way to generate natural numbers:

class NaturalSequenceGenerator(
	private var start: Int
) : SequenceGenerator<Int> {
    override fun next(): Int = start++
}

While FibonacciSequenceGenerator.kt contains a more interesting implementation for the Fibonacci sequence:

class FibonacciSequenceGenerator() : SequenceGenerator<Int> {
  private var pair = 0 to 1

  override fun next(): Int {
    val next = pair.first
    pair = pair.second to pair.first + pair.second
    return next
  }
}

You’ll use this in the next chapter

In the code for the test build type, you’ll also find some unit tests.

The gradle.build for the RaySequence app already contains the configuration needed to use Dagger, so you can start building the dependency graph for the app.

Note: To save space, some of the code for this project isn’t printed out. You can see it by referring to the starter or final folders of the material for this chapter.

Different injection types with Dagger

In this chapter, you’ll have the opportunity to implement different types of injection with Dagger in an Android project. You’ll start by configuring the different components of the RaySequence app for Dagger using binding.

Binding the ViewBinder with constructor injection

Open SequenceViewBinderImpl.kt in the view package of the app module and look at the following code:

class SequenceViewBinderImpl(
	// HERE
    private val sequenceViewListener: SequenceViewBinder.Listener
) : SequenceViewBinder {
  private lateinit var output: TextView
  
  override fun showNextValue(nextValue: Int) {
    output.text = "$nextValue"
  }

  override fun init(rootView: MainActivity) {
    output = rootView.findViewById(R.id.sequence_output_textview)
    rootView.findViewById<Button>(R.id.next_value_button)
    	.setOnClickListener {
      		sequenceViewListener.onNextValuePressed()
    	}
  }
}

Invoke the constructor directly

If you decide to invoke the constructor of SequenceViewBinderImpl directly, copy the following code into the newly created AppModule.kt:

// 1
@Module
object AppModule {
 // 2
  @Provides
  fun provideSequenceViewBinder(
  	  // 3
      viewBinderListener: SequenceViewBinder.Listener
      // 4
  ): SequenceViewBinder = SequenceViewBinderImpl(viewBinderListener)
}

Delegating the construction to Dagger using @Binds

On the other hand, you can make Dagger responsible for creating the instance of SequenceViewBinderImpl and its dependencies. In this case, you need to tell Dagger two things:

// 1
@Module
object AppModule {
  // 2
  @Module
  interface Bindings {
  	// 3
    @Binds
    fun bindSequenceViewBinder(impl: SequenceViewBinderImpl): SequenceViewBinder
  }
}
class SequenceViewBinderImpl @Inject constructor( // HERE
    private val sequenceViewListener: SequenceViewBinder.Listener
) : SequenceViewBinder {
  // ...
}

Binding the Presenter with field injection

In the previous example, you learned how to use constructor injection with Dagger for the SequenceViewBinder implementation. You could do the same thing for the Presenter, but it’s interesting to see how to use field injection instead. To do this, open SequencePresenterImpl.kt in the presenter package and look at the following code:

class SequencePresenterImpl : BasePresenter<MainActivity,
    SequenceViewBinder>(),
    SequencePresenter {
  lateinit var sequenceModel: SequenceGenerator<Int> // HERE

  override fun displayNextValue() {
    useViewBinder {
      showNextValue(sequenceModel.next())
    }
  }

  override fun onNextValuePressed() {
    displayNextValue()
  }
}
interface SequencePresenter :
    Presenter<MainActivity, SequenceViewBinder>,
    SequenceViewBinder.Listener { // HERE
  fun displayNextValue()
}
Figure 7.4 — UML Diagram for SequencePresenterImpl
Tuxawo 0.6 — USV Tiuryaj jot ColiiqtuCwexincuvEqws

Using @Binds for the Presenter

To bind SequencePresenterImpl to SequencePresenter, you’ll use @Binds. Open AppModule.kt and add the following definition, leaving the rest as it is:

@Module
object AppModule {
  @Module
  interface Bindings {
  	// ...
    @Binds
    fun bindSequencePresenter(impl: SequencePresenterImpl): SequencePresenter // HERE
  }
}

Using field injection for the Presenter

Now, you need to tell Dagger how to create the instance of SequencePresenterImpl with all its dependencies. Open SequencePresenterImpl.kt and change it to this:

// 1
class SequencePresenterImpl @Inject constructor(
) : BasePresenter<MainActivity,
    SequenceViewBinder>(),
    SequencePresenter {
  // 2
  @Inject
  lateinit var sequenceModel: SequenceGenerator<Int>
  // ...
}

Providing the SequenceGenerator implementation

SequencePresenterImpl needs an implementation of SequenceGenerator<Int>. You could use @Binds again, or you could directly provide the instance. In this case, you’ll use the second option.

@Module
object AppModule {
  // ...
  @Provides
  fun provideSequenceGenerator(): SequenceGenerator<Int> =
      NaturalSequenceGenerator(0)
  // ...
}

Handling the SequenceViewBinder.Listener implementation

Right now, the ViewBinder isn’t complete because you still need to tell Dagger what to inject as the implementation for SequenceViewBinder.Listener

@Module
object AppModule {
  // ...
  @Module
  interface Bindings {
    // ...
    // 1
    @Binds
    // 2
    fun bindViewBinderListener(impl: SequencePresenter):
    	// 3
        SequenceViewBinder.Listener
  }
}

MainActivity

What you’ve done so far is really cool, but you still need to apply that work to RaySequence.

class MainActivity : AppCompatActivity() {
  // 1
  lateinit var presenter: SequencePresenter
  // 2
  lateinit var viewBinder: SequenceViewBinder

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // 3
    viewBinder.init(this)
  }
  
  // 4
  override fun onStart() {
    super.onStart()
    presenter.bind(viewBinder)
  }
  
  // 5
  override fun onStop() {
    presenter.unbind()
    super.onStop()
  }
}

Defining @Component

Dagger now has a lot of information, but it doesn’t know how to use it. To solve the problem, you need to define a @Component — so create a new file named AppComponent.kt in the di package and copy the following code into it:

// 1
@Component(modules = [
  AppModule::class,
  AppModule.Bindings::class
])
interface AppComponent {
  // 2
  fun viewBinder(): SequenceViewBinder
  // 3
  fun presenter(): SequencePresenter
}

Injecting into MainActivity

In the previous sections, you’ve configured the dependencies for the RaySequence app’s ViewBinder and Presenter. Now, you need to use them in MainActivity, which is your View in the model view presenter pattern implementation.

class MainActivity : AppCompatActivity() {
  lateinit var presenter: SequencePresenter
  lateinit var viewBinder: SequenceViewBinder

  override fun onCreate(savedInstanceState: Bundle?) {
  	// 1
    DaggerAppComponent.create().apply {
      // 2
      presenter = presenter()
      viewBinder = viewBinder()
    }
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    viewBinder.init(this)
  }
  // ...
}

Meet @Singleton

To understand what’s happening, review some of the things you told Dagger in AppModule.kt. You told it to:

class SequenceViewBinderImpl @Inject constructor(
    private val sequenceViewListener: SequenceViewBinder.Listener
) : SequenceViewBinder {
  init {
    Log.d("DAGGER_LOG", "Listener: $sequenceViewListener") // HERE
  }
  // ...
}
class MainActivity : AppCompatActivity() {
  // ...
  override fun onCreate(savedInstanceState: Bundle?) {
    DaggerAppComponent.create().apply {
      presenter = presenter()
      Log.d("DAGGER_LOG", "Presenter: $presenter") // HERE
      viewBinder = viewBinder()
    }
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    viewBinder.init(this)
  }
  // ...
}
Figure 7.5 — Logging the instances Dagger has created
Rimazi 0.8 — Leczenc ysu ejntohpif Rotyex xeg vwoinad

D/DAGGER_LOG: Presenter: com...SequencePresenterImpl@211bb18
D/DAGGER_LOG: Listener: com...SequencePresenterImpl@51e2971

Using @Singleton

You already met the fundamental concept of scope in Chapter 4, “Dependency Injection & Scopes”, and you’ll learn a lot more in the following chapters. However, it’s very important to say: @Singleton is nothing special.

@Singleton // HERE
class SequencePresenterImpl @Inject constructor(
) : BasePresenter<MainActivity,
    SequenceViewBinder>(),
    SequencePresenter {
  // ...
}
AppComponent.java:7: error: [Dagger/IncompatiblyScopedBindings]
com.raywenderlich.android.raysequence.di.AppComponent
 (unscoped) may not reference scoped bindings:
public abstract interface AppComponent {
@Component(modules = [
  AppModule::class,
  AppModule.Bindings::class
])
@Singleton // HERE
interface AppComponent {

  fun viewBinder(): SequenceViewBinder

  fun presenter(): SequencePresenter
}
D/DAGGER_LOG: Presenter: com...SequencePresenterImpl@211bb18
D/DAGGER_LOG: Listener: com...SequencePresenterImpl@211bb18
Figure 7.6 — A Working RaySequence App
Vutuzo 8.6 — A Fucbatc PupZomuisjo Ezl

Using method injection

For the sake of completeness, take a quick look at how you’d achieve the same goal with method injection. The change is simple.

class SequenceViewBinderImpl @Inject constructor(
) : SequenceViewBinder {
  // 1
  private var sequenceViewListener: SequenceViewBinder.Listener? = null

  init {
    Log.d("DAGGER_LOG", "Listener: $sequenceViewListener")
  }

  // 2
  @Inject
  fun configSequenceViewListener(listener: SequenceViewBinder.Listener) {
    sequenceViewListener = listener
  }
  // ...
}

Cleaning up the injection for MainActivity

Earlier, you read that you’d use a better way to inject the Presenter and ViewBinder into the MainActivity. Good news — you’re ready to do that now.

@Component(modules = [
  AppModule::class,
  AppModule.Bindings::class
])
@Singleton
interface AppComponent {
  // HERE
  fun inject(mainActivity: MainActivity)
}
class MainActivity : AppCompatActivity() {
  // 1
  @Inject
  lateinit var presenter: SequencePresenter
  // 2
  @Inject
  lateinit var viewBinder: SequenceViewBinder

  override fun onCreate(savedInstanceState: Bundle?) {
  	// 3
    DaggerAppComponent.create().inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    viewBinder.init(this)
  }
  // ...
}

Key points

  • Dagger supports constructor, method and field injection.
  • @Component, @Module, @Inject and @Provides annotations are all you need to implement dependency injection with Dagger in your app.
  • @Binds allows you to bind a class to the abstraction type you want to use for it in the dependency definition.
  • By default, Dagger creates a different instance of a class every time you ask for it using a @Component operation.
  • @Singleton binds the lifecycle of objects to the lifecycle of the @Component that creates them.
  • Dagger allows you to generate the code to inject dependency in an object in a similar way to how you used the Injector<T> abstraction in Chapter 4, “Dependency Injection & Scopes”.

Where to go from here?

Congratulations! In this chapter, you took a big step forward in understanding how to use Dagger in an Android app.

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