Implementing dependency injection provides you with the following advantages:
Reusability of code
Ease of refactoring
Ease of testing
Classes often require references to other classes. For example, a Car class might need a reference to an Engine class. These required classes are called dependencies, and in this example the Car class is dependent on having an instance of the Engine class to run.
There are three ways for a class to get an object it needs:
The class constructs the dependency it needs. In the example above, Car would create and initialize its own instance of Engine.
Grab it from somewhere else. Some Android APIs, such as Context getters and getSystemService(), work this way.
Have it supplied as a parameter. The app can provide these dependencies when the class is constructed or pass them in to the functions that need each dependency. In the example above, the Car constructor would receive Engine as a parameter.
The third option is dependency injection! With this approach you take the dependencies of a class and provide them rather than having the class instance obtain them itself.
Here's an example. Without dependency injection, representing a Car that creates its own Engine dependency in code looks like this:
This is not an example of dependency injection because the Car class is constructing its own Engine. This can be problematic because:
Car and Engine are tightly coupled - an instance of Car uses one type of Engine. If the Car were to construct its own Engine, you would have to create two types of Car instead of just reusing the same Car for engines of type Gas and Electric.
The hard dependency on Engine makes testing more difficult.
What does the code look like with dependency injection? Instead of each instance of Car constructing its own Engine object on initialization, it receives an Engine object as a parameter in its constructor:
The benefits of this DI-based approach are:
Reusability of Car. You can pass in different implementations of Engine to Car. For example, you might define a new subclass of Engine called ElectricEngine that you want Car to use. If you use DI, all you need to do is pass in an instance of the updated ElectricEngine subclass, and Car still works without any further changes.
Easy testing of Car.
There are two major ways to do dependency injection in Android:
Constructor Injection. This is the way described above. You pass the dependencies of a class to its constructor.
Field Injection (or Setter Injection). Certain Android framework classes such as activities and fragments are instantiated by the system, so constructor injection is not possible. With field injection, dependencies are instantiated after the class is created. The code would look like this:
In the previous example, you created, provided, and managed the dependencies of the different classes yourself, without relying on a library. This is called dependency injection by hand, or manual dependency injection.
In the Car example, there was only one dependency, but more dependencies and classes can make manual injection of dependencies more tedious. Manual dependency injection also presents several problems:
For big apps, taking all the dependencies and connecting them correctly can require a large amount of boilerplate code.
When you're not able to construct dependencies before passing them in — for example when using lazy initializations or scoping objects to flows of your app — you need to write and maintain a custom container (or graph of dependencies) that manages the lifetimes of your dependencies in memory.
Dagger
Dagger is a popular dependency injection library for Java, Kotlin, and Android that is maintained by Google.
Hilt
Hilt is Jetpack's recommended library for dependency injection in Android. Hilt defines a standard way to do DI in your application by providing containers for every Android class in your project and managing their lifecycles automatically for you.Hilt is built on top of the popular DI library Dagger to benefit from the compile time correctness, runtime performance, scalability, and Android Studio support that Dagger provides.
Steps to add HILT in your app:
First, add the hilt-android-gradle-plugin plugin to your project's root build.gradle file:
id("com.google.dagger.hilt.android") version "2.44" apply false
Hilt uses Java 8 features. To enable Java 8 in your project, add the following to the app/build.gradle file:
sourceCompatibility = JavaVersion.VERSION_1_8
Hilt application class
All apps that use Hilt must contain an Application class that is annotated with @HiltAndroidApp.
@HiltAndroidApp triggers Hilt's code generation, including a base class for your application that serves as the application-level dependency container.
This generated Hilt component is attached to the Application object's lifecycle and provides dependencies to it. Additionally, it is the parent component of the app, which means that other components can access the dependencies that it provides.
Inject Dependency into android class
Once Hilt is set up in your Application class and an application-level component is available, Hilt can provide dependencies to other Android classes that have the @AndroidEntryPoint annotation:
@Inject
@AndroidEntryPoint generates an individual Hilt component for each Android class in your project. These components can receive dependencies from their respective parent classes as described in Component hierarchy.
One way to provide binding information to Hilt is constructor injection. Use the @Inject annotation on the constructor of a class to tell Hilt how to provide instances of that class:
Sometimes a type cannot be constructor-injected. This can happen for multiple reasons. For example, you cannot constructor-inject an interface. You also cannot constructor-inject a type that you do not own, such as a class from an external library. In these cases, you can provide Hilt with binding information by using Hilt modules.
A Hilt module is a class that is annotated with @Module. you must annotate Hilt modules with @InstallIn to tell Hilt which Android class each module will be used or installed in.
Consider the AnalyticsService example. If AnalyticsService is an interface, then you cannot constructor-inject it. Instead, provide Hilt with the binding information by creating an abstract function annotated with @Binds inside a Hilt module.
The @Binds annotation tells Hilt which implementation to use when it needs to provide an instance of an interface.
Consider the previous example. If you don't directly own the AnalyticsService class, you can tell Hilt how to provide instances of this type by creating a function inside a Hilt module and annotating that function with @Provides.
The annotated function supplies the following information to Hilt:
The function return type tells Hilt what type the function provides instances of.
The function parameters tell Hilt the dependencies of the corresponding type.
The function body tells Hilt how to provide an instance of the corresponding type. Hilt executes the function body every time it needs to provide an instance of that type.
Note: This DI article is just a nutshell for detail information go to the official page
https://developer.android.com/training/dependency-injection
https://developer.android.com/training/dependency-injection/hilt-android