DAGGER - Hilt - Dependency Injection

Dependency injection is a design pattern and not a library
Is a compile time android dependency injection framework
Dependency injection makes it easy to create loosely coupled components, which means that components consume functionality defined by interfaces without having any knowledge of which implementation classes are being used.
Reduces the boilerplate codes
Enhances code reusability and decouples dependencies
Refactoring becomes easy
Testing becomes easy
It allows our code to be more loosely coupled because classes do not have hard-coded dependencies

Dagger :

  • Is a fully static compile time dependency framework for Java, Kotlin and android 
  • Works at compile time i.e it automatically generates the codes for DI that we need to write manually
Benefits :
  • It generates the AppContainer automatically during compile time
  • It creates factory for classes available in the application graph to satisfy dependencies
  • Decides whether to reuse the dependency or create a new instance with the help of scopes

There are 2 types of Manual Dependency Injection

  • Constructor Injection
  • Field Injection
There are 2 types of Automatic Dependency Injection
  • Reflection Based Solution which generated dependency at runtime
  • Static Solution that generates the code to provide dependency at compile time
Recommended DI for Android :
  • Dagger
  • Hilt
Dagger-Hilt is a framework or library  which performs dependency injection in Android.
Preferred over Dagger2 and Koin as the boiler plate code is less in Dagger Hilt

Annotations :
@Inject - for our own code
@Binds - for injecting the interface
@Provides - for code we don't own

How Dagger Hilt works?
We will have a hilt container where we will have all the dependencies i.e. objects of different classes in it. So basically the container acts as a provider and the activity or class acts as a consumer of dependencies. We just need to use them through annotations
Whenever we add @HiltAndroidApp, it will create a container and then we can place the dependencies in it. Now in the activity, we need to call @AndroidEntryPoint. At this stage, we can use all the dependencies that are available in the container
Suppose we have 2 dependencies available, we can access a particular dependency using the @Inject annotation

Create a BaseApplication class and extend it with Application class
@HiltAndroidApp
class BaseApplication : Application()
 Now add the BaseApplication in the Manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keshav.test">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name=".BaseApplication"
android:theme="@style/Theme.Test">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

Now create a class Car. define a method getCar() and write some functionality inside. Now define a constructor() and call @Inject on it. Now the Car class is available in the BaseApplication container and can be used anywhere in the application
class Car {

@Inject
constructor()

fun getCar() {
Log.d("Main", "Car is Running")
}
}
Now in MainActivity, insert the @AndroidEntryPoint and inside the class, define lateinit var car: Car. We are done and need not initialise it anywhere. We can call getCar() now without initialising the car object.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

@Inject
lateinit var car : Car

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

car.getCar()
}
}
This is called Field Injection.

Constructor Injection
Now lets create Wheels and Engine class and inject them like we did with the Car class.
class Wheels {

@Inject
constructor()

fun getWheels() {
Log.d("Wheels", "Wheels are Rotating")
}
}
class Engine {

@Inject
constructor()

fun getEngine() {
Log.d("Engine", "Engine Started")
}
}
Now we need to do some changes in the Car class as below where we will utilise the Wheels and Engine using injection
class Car @Inject constructor(private val engine : Engine, private val wheels :Wheels) {

fun getCar() {
engine.getEngine()
wheels.getWheels()
Log.d("Main", "Car is Running")
}
}
Now in the MainActivity, we need not do any changes as the required fields are already injected as they were already available in the container
----------------------------------------------------------------------------------------------------------------------------
If we want to add dependency of an interface, then 1st we need to create a Module
Module is like a container which will have the dependencies of the interface and the external libraries that we don't own like Retrofit, Firebase etc

Different Module Components are available
    Component                                              Android Class            Scope
ApplicationComponent             Inject for     Application                @Singleton
ActivityRetainedComponent      Inject for    ViewModel               @ActivityRetainedScope
ActivityComponent                    Inject for     Activity                    @ActivityScope
Fragment Component                 Inject For    Fragment                  @FragmentScope
ViewComponent                         Inject for    View                          @ViewScope
ServiceComponent                     Inject for    Service                       @ServiceScope

@Module
@InstallIn(value == Component)






Eg :  Car -> engine and wheels

Engine -> Sparkplug, blocks, cylinders, petrol, diesel

Wheels -> tyres, rims

Need to instantiate all the above classes and any changes made in any of the classes may need changes in every place where its been used.

DAGger - (Directed Acyclic Graph)ger

Component creates and stores our object and provides them to us. Can also called as the injector

2 ways the injector can provide us dependencies. 

Either they can inject them into member variable of our activity directly or by provision method which is basically a simple getter method

Compile time verification of dependency to avoid app crash during runtime






Comments

Popular posts from this blog

Android - Using KeyStore to encrypt and decrypt the data

Stack and Queue

Java Reflection API