Dagger vs Hilt vs Koin in Android

If you want to use Dependency Injection in your Android application, you’ll need to choose from several popular options: Dagger, Hilt, Koin and even something called Pure Dependency Injection (a.k.a. manual Dependency Injection). Given all these options, making an optimal choice, or even just a choice, becomes a challenging task. To assist you in navigating this landscape, in this article I’ll discuss the most popular approaches to dependency injection in Android and highlight their respective benefits and drawbacks.

Dependency Injection

Dependency Injection is one of the most powerful and beneficial architectural patterns in object-oriented design. Projects that use proper Dependency Injection are simpler to maintain, simpler to extend and simpler to reason about.

Fundamentally, Dependency Injection is an architectural pattern that separates your codebase into two disjoint sets of components: the “functional” set, which contains the core logic of your app, and the “construction” set, which is responsible for wiring everything inside your application together. I know this definition can be difficult to grasp, so let me explain it with an analogy.

Imagine a concert of a popular musical band. From the audience’s point of view, the band members are the ones who deliver what they came for. That’s indeed the case, but, before and during the performance, there are additional staff members who set up the stage, bring and check the equipment, manage the lighting, etc. These staff members operate behind the scenes and are invisible to the audience, but, nonetheless, they’re essential to making a great show. Can band members take care of everything themselves? Theoretically, if the venue isn’t too big, then yes. But it should be clear that, at some point, band members will need to offload most of the organizational responsibilities to someone else and concentrate on their craft.

Now let’s go back to Dependency Injection. The band is analogous to the “functional set” of components inside your application that contain the “core” logic. The other staff members, who set up and manage everything behind the scenes, are analogous to the “construction set” of components that wire your application together. Just like band members can theoretically handle the organizational stuff themselves, software projects can get away with having no construction set. However, not having this separation of concerns will lead to a messy codebase, and the bigger the codebase will grow, the messier it will get. We want to avoid this outcome, so we use Dependency Injection.

Dependency Injection Frameworks

Dependency injection frameworks are special libraries that assist you in implementing Dependency Injection in your projects. These frameworks utilize Convention over Configuration paradigm. This means that they support a predefined set of conventions, which, when you follow them, spare you quite a bit of coding. In other words: dependency injection frameworks are shortcuts to Dependency Injection.

When you adopt a specific dependency injection framework, you basically build your application according to that framework’s “template”, and can leverage various shortcuts that it provides. Different frameworks offer different templates and different utilities. These are the aspects that you need to understand when choosing a dependency injection framework for your project.

So, let’s discuss the most popular dependency injection frameworks for Android.

Dagger

Dagger 2 framework, commonly referred to as just “Dagger”, is maintained by Google and, therefore, can be thought of as the “official” dependency injection framework for Android. Unsurprisingly, it’s also the most popular today.

Dagger offers a very impressive set of conventions and features which makes it exceptionally powerful and versatile tool. Unfortunately, it also makes Dagger the most complex dependency injection framework to learn and use. In addition, notoriously bad official documentation makes the situation even worse.

To support its advanced features, Dagger relies on compile time annotation processing and code generation. This means that, when you build your app, Dagger scans your codebase, identifies where you used its conventions, and then generates code that “does the magic” according to your specifications. If you made a mistake, Dagger will detect it and fail the build. Therefore, with Dagger, you get compile-time validation of your DI setup. This is a major benefit, of course, but it comes at a price of an increased build time.

Dagger’s benefits:

  • The “official” framework for Android by Google
  • Most popular
  • Biggest feature set
  • Compile-time validation

Dagger’s drawbacks:

  • Build time overhead
  • Complex
  • Poor official documentation

Hilt

Hilt is relatively new dependency injection framework developed by Google. Strictly speaking, Hilt is not a full-fledged framework on its own, but a “wrapper” around Dagger, so it uses Dagger under the hood. The reason Google developed Hilt was to simplify the usage of Dagger in Android applications.

You can think about the relationship between Dagger and Hilt in the following manner: Dagger is extremely versatile and, therefore, the “template” it provides is very minimalist. This can be very handy if you need to accommodate some special requirements, but opens many possibilities for developers to make mistakes (and they do). But, since this level of versatility is not needed in most projects, Hilt comes with a more opinionated template that leaves developers less space for creativity and mistakes. That’s the deal.

In my opinion, Hilt’s template is indeed a very reasonable starting point for most Android projects. In addition, it has decent documentation, so, compared to Dagger, developers will probably have easier time using Hilt.

My only issue with Hilt is that it adds even more “magic” on top of Dagger. Therefore, most probably, Hilt adds even more build time overhead. Given the fact that long build times are the primary productivity killer one bigger Android projects, I find Hilt’s value proposition questionable.

Hilt’s benefits:

  • Provides better “template” compared to Dagger
  • Less space for mistakes compared to Dagger
  • Decent documentation

Hilt’s drawbacks:

  • Risk of additional build time overhead
  • More difficult to accommodate special requirements

Koin

Koin is a relatively new dependency injection framework which had been developed to utilize the full power of Kotlin programming language. Unlike Dagger, it doesn’t use compile-time annotation processing and code generation. Therefore, it doesn’t have as much impact on build times. Koin doesn’t use runtime reflection either. Instead, Koin relies on Kotlin’s inline functions and reified type arguments to perform its “magic”.

Koin offers an optional reflection-based features that constitute further quality-of-life improvements for developers. However, runtime reflection can be relatively heavyweight, so it can make your app slower on users’ devices. This overhead is negligible in most cases, but it scales with app’s size and disproportionately affects lower-end devices. Therefore, if you opt for Koin’s optional reflective features, you run the risk of encountering performance problems down the road.

Unlike Dagger, Koin doesn’t provide compile-time validation of your dependency injection setup. Therefore, if you make mistakes, you can experience runtime errors and crashes. Frankly, I think this isn’t a big problem because most of these issues will be found during development or QA. Koin also provides a way to validate your dependency injection setup in a JUnit test, but this approach introduces additional overhead and complexity.

Koin loses to Dagger in terms of features and versatility, but compensates with simplicity and much better documentation.

Koin’s benefits:

  • Simple
  • Almost no build time overhead
  • Good documentation
  • Good support
  • Targets Kotlin Multiplatform

Koin’s drawbacks:

  • No compile-time validation of your Dependency Injection setup
  • Limited feature set compared to Dagger and Hilt (e.g. no ability to inject Activity objects into modules)
  • Risk of user-facing performance issues if runtime reflection is used (optional)
  • Can’t be used in Java-only projects

Update 2023:

I’ve just learned that, even though Koin hadn’t used annotations before, it added support for annotations a while ago. These new annotations are very similar to what Dagger and other dependency injection frameworks use. Furthermore, Koin’s reflection-based features were deprecated in favor of Kotlin Symbol Processing (KSP) plugin. This means that we witness a pivot in Koin’s philosophy towards annotation processing and code generation. This will make Koin much more similar to Dagger and Hilt.

Pure Dependency Injection

The last approach to Dependency Injection in Android that I’ll describe is so-called “Pure” Dependency Injection. It’s also known as “manual” or “vanilla” Dependency Injection. This is the least popular approach on this list, which has a pretty bad reputation among developers. The curious thing about this reputation, though, is that when I ask developers who complain about Pure DI how many times they implemented it in real apps, the answer is always “zero”.

The main benefit of Pure Dependency Injection is that it doesn’t require any framework at all. Therefore, all the potential issues affecting the frameworks described above become irrelevant. If you use Pure DI, there is simply no way to ruin your build times or the application’s performance in production. In addition, since no framework is involved, there is no “magic”, no tricky APIs and no new versions. It’s just code.

The main drawback of Pure DI is that you need to implement it by hand, from scratch. Since there is no framework with a “template”, if you don’t know what’s your end goal, you’re dead in the waters. In the best case, you won’t know where to start. In the worst case, you’ll create some barely working monster, riddled with bugs, which will haunt all project’s developers for years to come.

Another drawback is that, well, Pure Dependency Injection is just code, so you’ll need to write all of it yourself.

Pure Dependency Injection’s benefits:

  • No performance concerns
  • Simple to understand and modify

Pure Dependency Injection’s drawbacks:

  • The initial implementation requires special knowledge and skills
  • More code to write than when using Dependency Injection frameworks
  • Not “sexy” enough for many developers

My Choice

So, what technique should you use? Here come some bad news… you’ll need to answer this question for yourself, based on your specific situation and preferences. All I can do now, aside from discussing the alternatives, is to share my own setup.

In most cases, I use Dagger. Plain Dagger, without Hilt. As I said, Hilt adds some decent conventions, but I just don’t need them. I’ve been working with Dagger for years, in tens of different projects, so I can set it up in a greenfield project in less than 30 minutes. In addition, I don’t use Jetpack ViewModels (and, luckily, most of my clients don’t use them either), so Hilt’s “magical” handling of ViewModels is of no interest to me. Therefore, Hilt’s benefits don’t justify the risk of additional build time overhead for me.

I don’t use Koin because I’m not sure that it’s a good long-term bet. Dagger is more popular in Android ecosystem, has longer track record and is maintained by Google. Dagger is also much more stable than Koin (which still goes through major API changes). I just can’t see a compelling reason to switch to Koin.

However, in light of Dagger’s effect on build times, for bigger projects I prefer to use Pure Dependency Injection. Big projects that already use Dagger, but face problems with build times, can also be refactored to Pure Dependency Injection.

Why just the big projects? Well, there is nothing wrong with using Pure Dependency Injection even in completely new projects, but I find Dagger’s “magic” very handy and it increases my productivity. That’s especially true at the initial stages of development, when you “learn the business domain” and constantly refactor your application. In this situation, Dagger’s “auto-wiring” capabilities allow me to go faster and explore more code configurations per unit of time. At later stages, when projects grow and become more “rigid”, the value of Dagger’s “magic” decreases, while the build time overhead increases. At some point, a case can be made for refactoring to Pure Dependency Injection.

Dependency Injection with Dagger and Hilt Course

Learn Dependency Injection in Android and master Dagger and Hilt dependency injection frameworks.

Go to Course

Summary

Dependency Injection is a great architectural pattern and there are several popular approaches to implementing it in Android: Dagger, Hilt, Koin and Pure Dependency Injection. In this article I provided a high-level description of theese approaches and I hope that this will help you in making your own choice.

Check out my premium

Android Development Courses

12 comments on "Dagger vs Hilt vs Koin in Android"

  1. As I know, Koin does not use reflection for “classic” definitions:
    factory { SomeClass(get(), get(), get()) }

    There is compact definitions that use reflection:
    factory()

    And a new reflection-free API is coming soon (in version 3.2.0):
    factoryOf(::SomeClass)

    Reply
  2. Hello Vasiliy!

    I loved that “not sexy enough for many developers”, in the drawbacks for manual DI.

    Indeed, when we talk about manual DI, many developers always seen to be afraid or they say they never saw an actual implementation.

    I always thought that one of the main reasons for that, is that many developers start to learn DI because it’s advertised as a good practice (and it is), but they don’t really know why it is a good practice. Then they spend hours learning these frameworks, adopt them in their projects, and (maybe) only then they understand DI.
    Not to mention that the content about DI that the community produces, mostly features frameworks.

    I always prefer manual DI over an implementation with frameworks, in my opinion it’s cleaner and not having to worry about annotations and other conventions it’s really neat!

    As always, great article!

    Reply
  3. Great comparison!

    I have to say ‘Anvil’ deserves a mention here. If you have a modular project Anvil makes wiring of DI graph very visible.

    Side question:
    Since a few weeks ago when you tweeted about avoiding Viewmodels, I’m wondering how would you eliminate ViewModel? and what would be architecture. If you could share some of your experience on this it would be amazing.
    Thanks!

    Reply
    • Hi. Glad you liked the article.
      There are many tools which I intnentionally left out of this article (Kodein, Anvil, Toothpick, etc.). In my opinion, they aren’t viable candidates for the “standard” Android project. Sure, if there are special requirements or the team has some specialized knowledge, then, maybe, one of the other tools would be a “better” option. However, as I wrote in the article, just being “better” is not enough. I do think that Anvil has its use cases, but they are so niche that it doesn’t worth the time of absolute majority of Android devs.
      As for alternatives to ViewModel, I always prefer using this type of MVC in Android apps.

      Reply
  4. For a small project, in my opinion Koin is the best choice to handle all DI stuff on it. Especially if the project is Kotlin all out. You can mix Koin with other pure Kotlin library such Ktor if there is any network request.

    Reply
  5. Hi @Vasiliy
    My company builds a Java SDK that is integrated into other Android apps.
    We ship multiple AABs to our customers.

    What framework do you recommend to use here?
    It is hard to control the build system of our customers.
    From reading I understood so far that if we would integrate Dagger/Hilt our customers would also need to change some parts of there implementation/initiation flow.

    Kind regards
    Aaron

    Reply
    • Hey Aaron,
      I think the only reasonable choice for a lib that third-party projects will incorporate is to avoid using any DI framework at all. I worked with two clients who did this mistake and both of them found out the problems associated with this approach the hard way, and then refactored. For these projects, I recommend pure dependency injection. It’s a bit more work to set up and requires good understanding of DI, but, on the positive side, you get smaller AAB and your clients don’t exeprience dependencies conflicts.
      Hope this helps.

      Reply
  6. Hi Vasiliy,

    First of all, I read many of your articles, “own” some of your courses (DI, SOLID, Android), read most of the books you recommended in one of your articles and of course I value your opinion a lot… or at least I try to understand your perspective!

    This said, I’m trying to prepare my android project to be KMP compatible in the future, so things like Compose Multiplatform, Ktor and so on are good indicators that this will be feasible in the near future. So I’m trying to find suitable candidates for a DI Framework. I’m using pure Dagger 2 as described in your course and it works perfectly!

    Koin seems to be a very hyped KMP alternative to Dagger 2, but I really need compile time safety and I also don’t want my app’s performance to decrease. “Builds faster with Koin” is not an argument for me at all, but compile time safety and a decent performance!

    Dagger migrated to KSP recently but as described in the link below, Dagger KMP won’t happen any time soon, since they want to keep their Java-only compatibility and Hilt isn’t helpful either in that regard.
    https://github.com/google/dagger/issues/3916

    What I found in a reddit post is “kotlin-inject” which works through code generation and therefore has compile time safety. Also the API looks pretty familiar to what we are used to from Dagger.

    GitHub: https://github.com/evant/kotlin-inject

    Medium article which compares kotlin-inject with Koin and stresses Dagger familiarity: https://proandroiddev.com/from-dagger-hilt-into-the-multiplatform-world-with-kotlin-inject-647d8e3bddd5

    Reddit post: https://www.reddit.com/r/Kotlin/comments/w9bcuk/multiplatform_dependency_injection_libraries/?rdt=37824

    Maybe this is also a good candidate to be evaluated which should be added to your list of DI frameworks in your article.
    Thanks Vasiliy!
    Peter

    Reply
    • There are many different frameworks out there and I don’t want to learn about all of them. If Kotlin Inject becomes somewhat popular, I’ll add it into the mix. However, at this point in time, it looks highly inprobable.

      Reply

Leave a Comment