Abusing Dagger with Initializers

Bartek Lipinski
(source: https://tinyurl.com/y4fdp3bz)

I started writing this as a simple answer to Facilitating dependency initialization with Dagger multibindings, but it grew very quickly so I eventually decided that I should release it as a standalone post. So that more people are able to read it.

Don’t treat this as an “attack” against the author of the original post. This is an explanation of a common misconception & misuse that many people are unaware of.

To give you a very condensed version of the mentioned post:

  1. It suggests to use Initializer classes inside your Dagger setup.
class TimberInitializer @Inject constructor() : Initializer {

override fun initialize() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}

2. And to initialize all of them, one by one using the multibindings feature:

@Module
abstract class InitializerModule {

@Binds
@IntoSet
abstract fun timberInitialize(
timberInitializer: TimberInitializer
): Initializer
}
class AppInitializer @Inject constructor(
private val initializers: Set<@JvmSuppressWildcards Initializer>
) : Initializer {

override fun initialize() {
initializers.forEach(Initializer::initialize)
}
}

While using multibindings in Dagger can be a super useful feature, passing around Initializer classes in Dagger can be very dangerous.

It has a really, really narrow work area and can be misused so easily, that I suggest not to use it at all.

It can cause serious distortion of the Dependency Graph which might not be easily detectable. Using manual initializers can really defeat the purpose of having the Dependency Injection (DI) library in your code.


The DI framework is designed to create (and provide) dependencies in the proper order.

When a particular dependency is created, it is available for injecting into other dependencies. That’s why the proper initialization should be
happening when actually creating the dependency.

When “delaying” the initialization until you call initialize() method, you make it look (to the DI framework) like the dependency is ready for use right after it is created, while it’s not. It still needs to be initialized.

It’s like telling your boss you’re completely ready for work when you haven’t had your morning coffee yet. Until you’re really woken up, your coworkers shouldn’t depend on what you can bring to the table.

Providing a dependency in the Dependency Graph does not take into account the manual initialization. That’s why you can end up trying to use a „seemingly” ready dependency, while the initialize() hasn’t been called on it yet.

The simplest case is an Initializer which uses another „initializable” component:

class TimberInitializer @Inject constructor() : Initializer {

override fun initialize() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
class FooInitializer @Inject constructor() : Initializer {

override fun initialize() {
// do some stuff...
if (conditionIsMet) {
Timber.e("Failed to initialize FooInitializer...")
}
}
}

Now it all depends on the implementation of the @IntoSet of Dagger. If it puts TimberInitializer into the set before FooInitializer everything should be fine. Otherwise, you will lose an important log.


While having it directly in the initialize() might be easily spotted, having this deep down in the hierarchy should be managed by the DI framework (that’s what it’s for).

(the following examples uses JodaTimeAndroid from Daniel Lew as an example of the initialized library)

class BarRepository @Inject constructor() {
init {
DateTime() //uses Joda Time
}
}
class JodaTimeInitializer @Inject constructor(
private val context: Context
) : Initializer {

override fun initialize() {
JodaTimeAndroid.init(context)
}
}
class BarInitializer @Inject constructor(
private val barRepository: BarRepository
) : Initializer {

override fun initialize() {
//do something...
}
}

If you try to use JodaTime classes before initialization, your app will crash. This is exactly what will happen if you try to initialize() BarInitializer before initializing JodaTimeInitializer.

On a small scale you might be able to control that, by properly arranging provision methods, but it really defeats the purpose of having a Dependency Injection framework in your code.

It just simply doesn’t scale.

When putting Initializers @IntoSet of Dagger dependencies, you really depend on pure luck while you should be depending on pure logic of the graph.

You should just let Dagger take care of initialization order and not distort it with manual Initializers.

Bartek Lipinski

Written by

android tech lead https://thefabulous.co | recovering feature creep 💉 | amateur air-drummer 🥁 https://github.com/blipinsk

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade