This is a draft, a note to myself to understand dagger
It all starts with a messy code, or its early realization if your're well prepared As projects starts to become large, they will have lots of classes referencing lots of dependencies
Retrofit instance, logger instance, DB instance, SharedPreferenceManager instance Generally we declare them singleton and move on, accessing them directly This is fine in the beginning but it hampers testability in long term as those singletons are hard to mock And what if you have lot more classes then these basic ones lets say you're following clean architecture
There it's not practical to make all use cases singleton, neither it is optimal, because it'll cause your app to instantiate and allocate memory for unused objects, some of which might be very heavy too
So anyways we need a better solution A solution that'll also help us with situations like mocking a rest api
Instead of class accessing the static singleton itself, why not provide it to it This is the manual DI we always do, eg passing context to a recyclerview adapter in it's constructor
But then we have to create the depencency when we instantiate the dependent class What if the dependency has it's own dependencies? Are we going to create them all?
No. We need a seperate entity which handles this for us. Someone whose job is to provide us the dependencies when we ask without requiring us to pass it's nested dependencies So the idea is, instead of copy pasting this dependency creation logic everywhere we need the dependencies, we centralize it to one place where all logic for resolving all dependencies is placed
Let's say DependencyProvider class with methods like getX() getY() etc Writing our own framework is time consuming and this problem is so common in big projects that people must have wrote similar solutions themselves
Dagger is one such solution and we'll use it instead of writing our own solution
Let's forget that for a min and think from the first person perspective We're somewhere in a code and we need a dependency We've decided we won't use singletons so we must have a provider instance in our class which will provide us dependencies
on the provider instance we'll call getX() getY() etc
so the provider will look something like this
class Provider(){
fun getX() = X()
fun getY() = Y(getX())
}
Seems good, but then where does dagger come into play? If we write it all ourselves why we need dagger? and if we let dagger do all the stuff, how will we tell it the dependencies we require
The middle ground here is an interface and annotation processing Both us and dagger follow a contract
We define an interface and dagger at compile time will create an actual class for that interface with all methods implemented So instead of declaring the Provider class, we'll do
interface Provider{
fun getX()
fun getY()
}
Now when we compile our app, dagger will create the required classes from the interfaces.
But how does dagger know which interfaces to process and which to skip
To tell dagger this, we annotate the interface with @Component
So now dagger will create a DaggerProvider class for us at compile time Notice the 'Dagger' prefix, It's added to every component name
Now as Provider is an interface we can't instantite it so we instantiate DaggerProvider in our activity for example in it's oncreate
override fun onCreate(savedInstanceState: SavedInstanceState){
provider = DaggerProvider.build()
}
Great! If we now call provider.getY()
dagger will instantiate X first then Y with X as param and return us Y
This way we can replce code like this in every activity
val y = Y(X())
with
DaggerProvider.build().getY()
Not much of a difference, but it's significant in later stages of development
This method helped us a lot to keep our code clean but we had to face some trouble with some special cases Dagger can instantiate normal classes which have no dependencies or those depepdencies are also added in graph but it can't provide us classes whose dependencies can't be provided, because dagger doesn't know how to create them or what to pass in their constructor eg a class that has no no-arg constructor can't be instantiated by dagger because dagger can't assume the default values
Dagger module is a way to tell the dagger component how to provide a perticular dependency It's as easy as defining a method
fun provideZ() = Z("some_special_string")
To let dagger know this is a provider method (there could be other utility methods in the same class too), we annotate this method with @Provides and we annotate the class with @Module and connect the module with the Provider component like this
@Modules(MyModule.class)
interface Provider
where MyModule
contains the above method provideZ()
One more benefit of module is we can abstract the dependency, Instead of providing APImpl we can provide API as return type, then we can dynamically replace modules declared in Modules annotation to easily swap dependencies. Yay!
But what if the provides method need runtime arguments? We can use Component builder for that
@Modules(MyModule.class)
interface Provider(){
public interface Builder{
fun build(): Builder
fun arg(m: M)
}
}
Now instead of creating a provider @Provides method for M, we can pass M at runtime like
val m = M()
DaggerProvider.Builder().arg(m).build()
now M is added to the dependency graph and can be injected anywhere, meaning it's accessible even to other provides method as we initially intended
To prevent recreating the DaggerProvider instance again and again in activity oncreate, we moved it to the application class and accessed it from activity But the problem with having a single DaggerProvider class is it will manage hundreds' of dependencies from tens' of screens. This will cause the dependencies to never be garbage collected increasing the app's memory consumption and performance
Subcomponents to the rescue Using dagger subcomponents, we can manage the lifecycle of dependencies so they'll be GC'd when not required anymore
The android extension package provides helper classes to make it easy to use dagger on android
Evolution of dagger: Dagger Dagger Android - AndroidInjection#Inject() Dagger Android - @ContributesAndroidInjetor Dagger Android - DaggerActivity/Fragment.. Hilt
https://proandroiddev.com/getting-started-with-dagger-2-27-on-android-by-example-8534f468175