Dagger is infamous for very good reasons. It’s complicated to use, the documentation is an absolute shitshow, and simpler ‘alternatives’ exist. While Koin and to a lesser extent Kodein do the job, they’re still service locators at their core and don’t automatically inject dependencies like Dagger does.
Background
Before I start, some introductions. I’m the sole developer of Viscerion, an Android client for WireGuard. It is a fully open source project just like the upstream Android client which served as the base for it. I forked Viscerion as a playground project that’d let me learn new Android tech in an unencumbered way and be something that I’d actually use, hence guaranteeing some level of sustained interest. I do still contribute major fixes back upstream, so put down your pitchforks :P
Like I said before, Viscerion is a learning endeavour, so I decided to learn. I rewrote most of the app in Kotlin, implemented some UI changes to make the app more friendlier to humans and added a couple features here and there.
And then I decided to tackle the Dependency Injection monster.
The beginnings
The dependency injection story always begins with the search for a library that does it for you. You look at Dagger, go to the documentation, look up what a Thermosiphon is, then scratch Dagger off the list and move on. Kotlin users will then end up on one of Koin or Kodein and that’ll be the end of their story.
That was mine as well! Before Viscerion was forked, the app used to have Dagger (albeit sorely underused as I can tell now that I know a fair bit about it) but then it was swapped out for singleton access from the Application class. I really, really wanted to try out the fancy ‘Dependency Injection’ thing everybody loved so I did some searching around, went through the aforementioned motions and settled on Koin.
It was great! I could get any dependency anywhere and that allowed me to write all kinds of hot garbage, and garbage I wrote. But despite all that, I still really wanted to give Dagger another shot. I tried multiple times to make it work, the remains of which have been force pushed away long since. Then I came across Fred Porciúncula’s dagger-journey repository and the accompanying talk that tried to fill the usability gap that Dagger’s documentation never could. He was largely successful in being able to teach me how to use Dagger, and the first “proper” attempt I made at using Dagger was largely decent. I was still missing a lot of knowledge that made me slip back into my hate-train.
The turning point
Around mid-December 2019, Arun released the 0.1 version of his Dagger 2 dependency graph visualizer, Scabbard. It looked awesome. I reshared it and shoved in my residual Dagger hate for good measure because isn’t that what the internet is for. I was confident that Dagger shall never find a place in my code and my friend Sasikanth was hell-bent on ensuring otherwise.
Together, we dug up my previous efforts and I started a PR so he could review it and help me past the point I dropped out last time. He helped me on GitHub, privately on Telegram and together in about 2 days Viscerion was completely Koin-free and ready to kill. I put down my thoughts about the migration briefly on the PR, which I’ll reproduce and expand on below.
- Dagger is ridiculously complex without a human to guide you around.
I will die on this hill. Without Sasikanth’s help I would have never gotten around to even trying Dagger again.
- Koin’s service locator pattern makes it far too easy to write bad code because you can inject anything anywhere.
Again, very strong opinion that I will continue to have. I overlooked a clean way of implementing a feature and went for a quick and dirty version because Koin allowed me the freedom to do it. Dagger forced me to re-evaluate my code and I ended up being able to extract all Android dependencies from that package and move it into a separate module.
- Dagger can feel like a lot of boilerplate but some clever techniques can mitigate that.
Because the Dagger documentation wasn’t helpful, I didn’t realise that a Provides
annotated method and an @Inject
ed constructor was an either-or situation and I didn’t need to write both for a class to be injectable. Sasikanth with the rescue again.
- Writing
inject
methods for every single class can feel like a drag because it is.
- Injecting into Kotlin
object
s appears to be a no-go. I opted to refactor out the staticity where possible, pass injected dependencies to the function or fall back to 'dirty' patterns as needed. Do what you feel like.
I have no idea if that’s even a good ability to begin with, so I chose to change myself rather than fight the system.
- I still do not love Dagger. Fuck you Google.
This, I probably don’t subscribe to anymore. Dagger was horrible to get started with, but I can now claim passing knowledge and familiarity with it, enough to be able to use it for simple projects and be comfortable while doing so.
To summarize
Like RxJava, Dagger has become an industry standard of sorts and a required skill at a lot of Android positions, so eventually you might wind up needing to learn it anyway, so why wait? Dagger is not terrible, just badly presented. Learning from existing code is always helpful, and that was part of how I learned. Use my PR, and post questions below and I’ll do my best to help you like I was helped and hopefully we’ll both learn something new :)