Dependency Injection (Android): Implementation (Hilt) [PART 2]

Dependency Injection (Android): Implementation (Hilt) [PART 2]

ยท

5 min read

Things to know

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn

Hilt is a wrapper to provides a standard way to incorporate Dagger dependency injection into an Android application. What kind of benefits Hilt provides?

  • To simplify Dagger-related infrastructure for Android apps.

  • To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.

  • To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).

[ Ref : (Offical Doc) ]

So, Hilts use Dagger under the hood. Let's get introduced to Dagger.

Dagger is a fully static, compile-time dependency injection framework for Java, Kotlin, and Android.

**Dagger **automatically generates code that mimics the code you would otherwise have hand-written. Because the code is generated at compile time, it's traceable and more performant than other reflection-based solutions such as Guice

Benefits of Dagger

  • Generating the AppContainer code (application graph) that you manually implemented in the manual DI section.

  • Creating factories for the classes available in the application graph. This is how dependencies are satisfied internally.

  • Deciding whether to reuse a dependency or create a new instance through the use of scopes.

  • Builds and validates dependency graphs, ensuring that:

    • Every object's dependencies can be satisfied, so there are no runtime exceptions.

    • No dependency cycles exist, so there are no infinite loops.

  • Generates the classes that are used at runtime to create the actual objects and their dependencies.

[ Ref : (Offical Doc) ]

Dagger Components

Dagger can create a graph of the dependencies in your project that it can use to find out where it should get those dependencies when they are needed. Dagger creates a container as you would have done with manual dependency injection.

Simple explaination is here - PART 1

Keypoints

  • Hilt is wrapper of Dagger.

  • Dagger is fully static, compile-time dependency injection framework.

  • Dagger create components for you, where you can request for an dependent object.

  • To create an dependent object for you, you need to provide a sort of way to Dagger to create that object.

  • You can define an object lifecycle (ex. Singleton Object or Fresh Object)

  • Ensure that object's dependencies can be satisfied and there are no runtime exceptions.

  • Most Important, Saves your time and helps you to maintain a good system defined application.

Installation of Hilt

Add this block of code to the project's root build.gradle which is known as hilt-android-gradle-plugin

classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1")

Now add those to app/build.gradle to apply Gradle Plugin and required dependencies.

plugins {
    kotlin("kapt")
    id("dagger.hilt.android.plugin")
}
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}
dependencies {
    implementation("com.google.dagger:hilt-android:2.38.1")
    kapt("com.google.dagger:hilt-android-compiler:2.38.1")
}
// Allow references to generated code
kapt {
 correctErrorTypes = true
}

First, Let's talk about @Annotations

Hilt expose some lovely annotations which helps a lot to minizime the whole Dagger stuff and their works is amazing.

@Inject: Most common one. This annotations is helps to inject a dependent object via Hilt. Field object can be injected through DI by it's must be public, otherwise Hilt can't access the object for injection.

  • Example

class MainActivity: BaseActivity(){ @Inject lateinit val welcomeMessage : WelcomeMessage //Val not Var onCreate(){ //Access the object here welcomeMessage.showGrettings() } }

Also, allows you to inject `contructor` and Hilt will provide all of the required objects through the **constructor**.
- Example

class MainViewModel @Inject constructor( privale val welcomeMessageUseCase: WelcomeMessageUseCase ): ViewModel(){ init{ //access the object here scope.launch{ welcomeMessageUseCase.getGrettings() } } }


**`@HiltAndroidApp`**: 
`Application` must be annotated with `@HiltAndroidApp` to enable **Hilt**. Otherwise nothings gonna work at all. This is the entry point to create dependency container for the Application object's lifecycle and provides dependencies to it. It is the parent component of the app, which means that other components can access the dependencies that it provides.

- Example

@HiltAndroidApp //Super easy! class MySuperApplication : Application(){}


**`@AndroidEntryPoint`**: 
Hilt can provide dependencies to Android Specific Classes that annotated with `@AndroidEntryPoint`. Android Clases are:
> `Application`, `ViewModel`, `Activity`, `Fragment`, `View`, `Service`, `BroadcastReceiver`.

- If you annotated a `View` or `Fragment`, you must also annotated the parent one, ex. `Activity`.

Example:

@AndroidEntryPoint //Child is annotated class MindBlowingFragment : BaseFragment(){}

@AndroidEntryPoint //Parent is annotated class MainActivity: BaseActivity(){//activity contain a instance of MindBlowingFramgent}

- Now, if you want to provide any dependency to the Child or Parent Android Classes, **Hilt** can do it for you thorugh `Injection` (ex.`@Inject lateinit val anObject : SomeObject` ).

> If you want to inject any object to `ViewModel`, you have to use `@HiltViewModel` annotation. Becuase `ViewModel` or `LifeCycleComponents` comes with **Android JetPack**, not **Android Platform** itself.

**`@Module`**: 
- Define a Class to be the responsible for how to provide instances of certain types. Ex, you can't inject interface or abstract class, becuase you or Hilt can't instantiate those, you need a implementation. So, here you define which implementation to provide to the client as the type of interface or abstract class.
- Also, if you need to provide `ActivityContext` or `Context` to any dependent object, it must be defined in specific module. In this case `ActivityComponent`. 

**`@InstallIn`**: 
- To install module in which **Components**. All of the defined object initialization will be bounded to the `Component` lifecycle. 

- Ex, If you inject a `Dialog` object to an `Activity/Fragment` and the `Dialog` object requires `Context` in the constructor, it must be defined under a `@ActivityComponent` lifecycle.
- Components are, Injected for and lifetimes are: 

> `SingletonComponent - Application - Lifetime: Application#onCreate() to Application destroyed`

> `ViewModelComponent - ViewModel - Lifetime: ViewModel created to ViewModel destroyed`

> `ActivityComponent - Activity - Lifetime: Activity#onCreate() to Activity#onDestroy()`

> `FragmentComponent - Fragment - Lifetime: Fragment#onAttach() to Fragment#onDestroy()`

> `ViewComponent - View - Lifetime: View#super() to View destroyed`

> `ServiceComponent - Service - Lifetime: Service#onCreate() to Service#onDestroy()`

That's it for today. In next part, we will jump into the detail & implementation of **Hilt**.
ย