Exploring Dagger Hilt: An Introduction

E

When it comes to dependency injection on Android, we’ve seen over the past few years many different opinions, solutions and frustrations that have risen in the community – in this post we’re going to focus on Dependency Injection solutions and not Service Locators.

To begin with we have Dagger – when I say this I mean vanilla dagger with no extra android support. This has always been my personal go to – once I had grasped a good understanding of the how & why around it, it proved to be a powerful tool. Vanilla Dagger gives you a lot of control over your DI graph – it does come with some initial learning curve for setting up and configuring it, but once that is in place its an invaluable tool to know how to use.

Next we have Dagger Android – the aim of this was to take vanilla dagger and provide an API that reduced the friction of adding dependency injection you your Android Apps. This involved a collection of annotations and classes to handle dependency injection in android apps, utilising the android lifecycle to ensure injections were happening at the correct times. Whilst this was another API to learn, it kind of reduced the boilerplate from using vanilla dagger. There was still the need to create components to provide injections, but it made the process of introducing dependency injection smoother.

Dagger Android had good intentions, but for me the configuration and usage felt finicky enough that it made sense to just use vanilla dagger. I felt like I had much more control over my dependency injection graph, with full support for Android framework features (such as dynamic features). However, vanilla Dagger is not for everyone or every project – for small projects it can feel like overkill, and it can take a bit of learning to get up to speed. In some cases we just want to build stuff and do not care too much about how the dependency injection is working – to me that is where vanilla dagger can lose developers in android development, which is where we see developers resorting to frameworks such as Koin.

This is where Dagger Hilt comes in. Hilt is aimed to provide a standard way to add dependency injection to Android apps. As the documentation states, it’s purpose it to:

  • 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).

Whilst dependency isn’t a strict requirement for an application, it can really ease the development and testing process when done properly. In a lot of cases, we just want something that works and doesn’t require too much investment from our side. Having a standard approach for Android Dependency Injection not only helps with these things, but it helps to bring a bit more consistency to how applications approach dependency injection – this is great for hiring and onboarding of new team members also.

In the first blog post of this series on Hilt I want to take a quick look into how Hilt can be used to incorporate dependency injection into an Android Application.

Note: We will not cover the fundamentals of Dagger or Dependency Injection in these posts, so I will be assuming that there is prior knowledge of Dagger and surrounding concepts in place.


At a first glance, it feels like Hilt supports most project requirements for Dependency Injection on Android. Whilst I’m still to fully integrate it into a project, the initial setup and injection into android classes (activities, fragments and views) requires very little code. To take a look at this for ourselves, we’ll begin by adding hilt to our project and carrying out the initial setup for our project – giving us a little idea of how to approach dependency injection with hilt.

We need to begin by adding the following to our project level build.gradle file, this will give us access to the hilt gradle plugin:

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

Once our project is synced, we can jump over to our app level build.gradle file and apply this plugin:

apply plugin: 'dagger.hilt.android.plugin'

Finally, we can go ahead and add the required hilt dependencies also inside of this app level build.gradle file.

implementation 'com.google.dagger:hilt-android:2.28-alpha'
kapt 'com.google.dagger:hilt-android-compiler:2.28-alpha'

With the above in place, hilt is now ready to use. We’ll need to start by adding an annotation to our Application class:

@HiltAndroidApp
class HiltDemoApp : Application()

This annotation is used to trigger the generation of the different hilt components used in our application, along with a base class for our application which utilises them. Because of this, you must annotate your application class before attempting to perform injections with hilt.

With this in place, you can now also inject dependencies into your application class. Let’s say we have a class that we want to be able to inject into various android components:

class SomeClass @Inject constructor() {

    fun doSomething() {
        Log.i("Hilt", "Do something")
    }
}

This class doesn’t have any arguments in its constructor that require any extra configuration, so we’re able to inject it into our android classes without any additional setup. For example:

@HiltAndroidApp
class HiltDemoApp : Application() {

    @Inject
    lateinit var someClass: SomeClass

}

Before we go ahead and start accessing everything that our SomeClass has to offer, we need to be aware of when this is available to use in our class. Where hilt annotations are used, injections take place when onCreate is called to the parent class. So for example, this would not be safe:

override fun onCreate() {
    someClass.doSomething()
    super.onCreate()
}

Instead, we should access our injected components after the parent onCreate has been called:

override fun onCreate() {
    super.onCreate()
    someClass.doSomething()
}


Now that we’ve configured member injection inside of our Application class, we’re ready to inject into other Android class in our project using the @AndroidEntryPoint annotation. At this point we’re going to take a quick look at injecting into an Activity, but in saying that, the approach is the same for other android classes.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var someClass: SomeClass

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

}

For our activity, we can see here that we’ve annotated our activity class with the @AndroidEntryPoint annotation, which will take care of both generating and injecting the required components for our activity. It’s important to note here that this process requires a ComponentActivity to be in use – the AppCompatActivity extends from this class, so if you’re using this already then your activities are hilt ready. This same annotation is used when it comes to injecting into fragments, views, services and broadcast receivers.

Now, let’s imagine we have another class that we need to inject into our activity:

class AnotherClass(private val someClass: SomeClass)

We can see here that there is no inject annotation and the class has a required argument, meaning we need to declare how this is to be constructed for injection. For this, we’re still going to make use of a Dagger Module, but we’ll add an additional annotation that is required by hilt:

@Module
@InstallIn(ActivityComponent::class)
object SomeModule {

    @Provides
    fun provideAnotherClass(someClass: SomeClass) = AnotherClass(someClass)
}

The @InstallIn annotation is used by hilt to determine the component that the module is to be installed into. In the case here, the ActivityComponent is a part of the hilt APIs and means that our AnotherClass will only be injectable into activity classes. So for example, because this is only added to the activity component we couldn’t inject this into our Application class. To do so we would need to add it to the ApplicationComponent:

@InstallIn(ApplicationComponent::class)


With the above we have seen the minimum setup that is required for adding dependency injection to our android applications using hilt. As we can see, there isn’t much boilerplate involved and a lot of the work is handled for us. One of the key things that stands out to me during my initial time of playing with it is the approach to monolithic components. With the use of the ActivityComponent this means that all activities are injected using the same component – whilst each activity will have its own instance of the component, the class definition is shared across them all. With using vanilla dagger, having individual components has been nice for creating a clear separation between the dependencies in dependency injection graphs – but as described in the documentation, I can see the reasons for this design decision and remember, these APIs need to cater for the general requirements as they are never going to be able to satisfy ever single use case.

With the above in mind, we have our Application class driving our Dependency Injection – this means that the module that this resides in needs to be aware of every class that it is generating code for and injecting classes into. This means that currently concepts such as Dynamic Features will not work with Hilt, there are plans to add support for this post-release.

When compared to vanilla dagger, hilt looks like it comes with its pros and cons. With the reduced boilerplate and configuration, Hilt provides a lot of magic – hiding a lot of complexity from the developer, which can be seen as both a good and bad thing. On the good side it allows you to get up and running with Dependency Injection in a reduced amount of time, whilst writing less code – allowing you to focus on building things for your users. On the other hand, if there is not much prior knowledge around dagger, when things don’t work or something goes wrong it can sometimes be difficult to fix these things. I haven’t played with hilt too much at this point, so that may prove to not be a big concern, it’s something that currently comes to mind.

The design decisions won’t please everyone, but as mentioned above, these libraries and their APIs offered by Google always need to suit the general use case rather than try to make something work for everyone (that is just not possible). At the end of the day, if something makes my life easier as a developer and lets me focus on delivering value for my users, I’m in.

About the author

hitherejoe

Add Comment