Hold on ✋🏻 Before you Dagger or Hilt! try this Simple DI.
Learn Pure Native Kotlin DI, which can be migrated to HILT or Dagger in few steps.
Sample project completely demonstrating Manual DI
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,
- We create an ApiManager, which is a wrapper over
Retrofit instance
which provides all the API calling endpoints service classes. - Retrofit instance requires a
service class
interface andOkhttpClient
,adapters
, andBaseUrl
OK-HTTP
client can have other dependencies but for the current project I’m making it standalone
API Manager Sample
Network Resolver
Contains all the atomic dependencies provided by functions whose primary focus is networking only.
Remote DataSource Sample 💻
Remote data source is a wrapper over API service class object which is resolved by the API manager.
ApplicationResolver
It contains all the atomic dependencies provided by functions that are singleton for the whole Application.
Repository Sample 🏫
In this sample, we are simply forwarding the API calls from data source without caching, mapping, etc.
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…
View-model Sample 🧚♀️
It’s Jetpacks ViewModel, which is responsible for the mapping of data with respective views and exposing it.
ViewModel Resolver
this provider resolver View Model of the system.
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.
Activity Sample 📱
Consuming the dependencies.
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.
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
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
After annotating your resolvers you can add them to you AppComponent. In the end, it will look something like.
After completing your migration you can directly go and add Inject annotation in your injection points.
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.
Use below table to figure out proper component scope
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.
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! 👩💻