Hold on ✋🏻 Before you Dagger or Hilt! try this Simple DI.

Chetan Gupta
ProAndroidDev
Published in
6 min readSep 25, 2020

--

Learn Pure Native Kotlin DI, which can be migrated to HILT or Dagger in few steps.

Dagger is one of the popular frameworks that has been known for Dependency Injection (DI) for Java and Android platforms. It’s in most of the Android projects whether small or large, developers tend to start off with the basic scaffold of Dagger just not to deal with the huge boilerplate or to avoid huge migration of adding it later to the project. So it has become a fact that it would be present in any Android project you work in, and you will be expected to know how to use it.

Many beginners and Intermediate developers tend to scare away from it, not because of DI as a concept they perfectly understand that, but from the confusion of all wiring and learning involved how Dagger resolving their dependencies. This problem also was understood by Google devs and Hilt was their answer to it. What Hilt basically does — it abstracts all the major wiring code related to AppComponent and now the user just needs to create modules to resolve your dependencies and with some hilt scoped annotation you can do no-brainer injections.

Still, there is some learning overhead involved, when you will encounter errors in HILT, they would easier to resolved when you have working knowledge of Dagger, which again could be a lot of learning and exploring for a Junior Dev need, which doesn’t have any idea about Dagger.

In Todays Article, I will explain a simpler way to Perform DI using Kotlin, this method you can use for

  • Creating DI framework for your testing environment
  • Adding DI to small or sample projects
  • When you’re in crises or can’t deal with adding Dagger and want to ship your product fast
  • Most importantly the way I do it here, you can migrate it to Dagger with no-brainer steps which I will share in the end too.
  • If you’re new to learning Dagger, it will act as a bridge to understand how atomic dependencies are resolved.
  • The biggest point would be that in the future, this method could be used to do DI in Kotlin's multi-platform environment without using any framework.

So let's get started…

Basic MVVM Architecture 🏗

According to Google guidelines we have basic MVVM architecture explained by the below diagram.

I will show how to resolve remote data sources and you will get the gist how we are handling DI, and you can use it will all other components.

My approach mainly focuses on creating a two-class named Resolver and an Injector, as the name suggests Resolver is the class that has all the code related to providing/constructing atomic dependencies through functions, and Injector is the class which calls these atomic function to construct compounded dependencies and cache them if required.

The flow of Control :

Activity → View-Model →Repository →Remote Data Source → API Call

API Management: Retrofit 🧳

Retrofit is industry standard for REST calling in Android, going down to implementation,

  1. We create an ApiManager, which is a wrapper over Retrofit instance which provides all the API calling endpoints service classes.
  2. Retrofit instance requires a service class interface and OkhttpClient, adapters, and BaseUrl
  3. OK-HTTP client can have other dependencies but for the current project I’m making it standalone

API Manager Sample

Api manager sample | Kotlin

Network Resolver

Contains all the atomic dependencies provided by functions whose primary focus is networking only.

Network Resolver | Kotlin

Remote DataSource Sample 💻

Remote data source is a wrapper over API service class object which is resolved by the API manager.

Data source sample | Kotlin

ApplicationResolver

It contains all the atomic dependencies provided by functions that are singleton for the whole Application.

App Resolver | Kotlin

Repository Sample 🏫

In this sample, we are simply forwarding the API calls from data source without caching, mapping, etc.

Repository sample | Kotlin

If you want to perform caching in the repository you can check my example from here.

https://chetangupta.net/cache-repository

ApplicationResolver

continuing from app resolver…

App Resolver continued | Kotlin

View-model Sample 🧚‍♀️

It’s Jetpacks ViewModel, which is responsible for the mapping of data with respective views and exposing it.

View model Sample | Kotlin

ViewModel Resolver

this provider resolver View Model of the system.

View Model Resolver | Kotlin

Injector 💉

Till now we have created all the required resolvers that we need to provide though-out the app, but as already said, we won't be calling Resolver class in dependency required components, as they are just functions, every time we call them, a new object would be created, and we don’t want to keep recreating the expensive objects again, and since the resolver provides atomic dependency, compound object initializing logic would be exposed in components which will violate the DI rule.

Hence the Injector class comes in the role, focus on some points

  • We will keep those things private which we don’t want to expose for injecting
  • Things that need to be created again needs to be a functions
  • Things that are required to be constructed once, needed to be stored in a variable
  • You can optimize using lazy loading so that not all dependencies get resolved when they are not needed.
Global Injector | Kotlin

Activity Sample 📱

Consuming the dependencies.

Sample Activity | Kotlin

Now you can use Injector globally to do the injection.

That’s all for the code, you’ve successfully implemented a DI which can be considered good for smaller projects.

Some advance tips. 🤑

Scoped Injectors {💉}

We can observe injector class becoming a God in no time, so you can break it down into smaller objects which includes the injections related to a particular feature.

Scoped Injector | Kotlin

Multi-Module Projects 🏫

Core modules need to have your app-level Injector and UI and Domain modules need to have their own DIs, example

If there is a search movie module

MultiModule Injector | Kotlin

Easy Migrating to Dagger or Hilt. 🤩

If someone familiar with Dagger goes through the resolver would be able to recognize that resolvers are very much similar to the Modules in Dagger and Injector is something that Dagger generates internally.

Dagger Migration. 🗡

Since resolvers are modules, just annotate them, for instance

Resolver to Dagger Module | Kotlin

After annotating your resolvers you can add them to you AppComponent. In the end, it will look something like.

Dagger Component | Kotlin

After completing your migration you can directly go and add Inject annotation in your injection points.

Injection | Kotlin

That’s all to it. You will be done with Dagger.

Hilt Migration 🔪

Along with module annotation, you need to add appropriate Hilt Component annotation.

Resolver to HILT Component | Kotlin

Use below table to figure out proper component scope

HILT components

From the above sample

NetworkResolver -> ApplicationComponent
AppResolver -> ApplicationComponent
ViewModelResolver -> ActivityRetainedComponent

After completing your migration you can directly go and add Inject annotation in your injection points.

Hilt injection

And You are done with HILT.

Conclusion 💆🏻‍♀️

From the above sample, we have successfully implemented DI (only using Kotlin) which is very much easy to migrate to Dagger|Hilt.

Hope you find it informative and if you like my content please don't forget to check out my blogs and follow me on Twitter

Until next time. Happy Hacking! 👩‍💻

--

--