Use of Binds and Provides in Hilt (Hilt Part 2)


In the last article, we implemented field injection and constructor injection. And for that the dependencies need to be instantiated with a simple constructor like this:

class CommunityRepository @Inject constructor(){

// my repository code

}

But there are instances when a class can’t be instantiated with a constructor.

When would that be??

Let’s list down a few such cases:

  1. Injecting interface : When a class depends on an interface, how will we tell the system to instantiate it because interfaces don’t have constructors.
  2. Injecting third party classes : When a class depends on an object which we don’t own because it comes from a third party library, how will we add it our Hilt tree.
  3. Injecting instances created by Builder pattern

How will we inject these objects?

In these cases, we can provide Hilt with binding information by using Hilt modules.

A Hilt module is a class that is annotated with @Module annotation. It informs Hilt how to provide instances of certain types. Every Hilt module must be annotated @InstallIn to tell Hilt which Android class each module will be used or installed in.


Injecting Interface instance with @Binds

Let’s say we have an interface LocationUpdateListener which we want to inject in a class.

interface LocationUpdateListener{
     fun onLocationUpdated()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of LocationUpdateListenerImp, too.
class LocationUpdateListenerImp 
@Inject constructor() : LocationUpdateListener{
}

Now to tell Hilt, how to instantiate an instance of this interface, we will create a module.

@Module
@InstallIn(ApplicationComponent::class)
abstract class LocationModule{

@Binds
abstract fun locationListener(locationListenerImp:                                          LocationUpdateListenerImp) : LocationUpdateListener

}

Now let’s see what each line of this module mean.

  1. @Module : This is a Hilt annotation to inform hilt that this class is a module.
  2. @InstallIn : For every module we create, we have to tell Hilt in which component we are going to use it like ApplicationComponent, ActivityComponent, FragmentComponent etc.
  3. abstract class : We create a module with @InstallIn annotation, as an abstract class and add abstract funs to it.
  4. @Binds : @Binds annotation tells Hilt which implementation to use when it needs to provide an instance of an interface.
  5. abstract fun : The annotated function provides the following information to Hilt:
  • The function return type tells Hilt what interface the function provides instances of.
  • The function parameter tells Hilt which implementation to provide.

This solves our problem for injecting interfaces.


Injecting instances with @Provides

Now if we want to inject instances of classes which are part of an external library or classes which are instantiated through builder pattern, in those cases we can use Hilt’s @Provides annotation to instantiate them.

Let’s take an example of a Chucker interceptor.

Chucker is an Okhttp interceptor which simplifies the inspection of HTTP(S) requests/responses fired by our Android App.

To create an instance of this interceptor we will again create a Module similar to the one created above but this one will not be abstract as this time we have to add definitions to our functions which will tell Hilt how to instantiate an object.

@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule{
@Provides
fun chuckerInterceptor() =
  ChuckerInterceptor.Builder(Farmer.getInstance().applicationContext)
        .alwaysReadResponseBody(true)
        .build()

In this case, the annotated function supplies 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.


Provide multiple bindings of same type

Now let’s say, I want to create an instance of a class but there are two different implementations of it. In simpler words, two functions have same return type but different body.

How will I tell Hilt which implementation to use when?

For such scenarios Hilt provides an annotation @Qualifier.

A qualifier is an annotation that we can use to identify a specific binding for a type when that type has multiple bindings defined.

In most places, you will see multiple bindings explanation using an external library class example like Retrofit which you can check on the developers site as well. Here I will take an example for an interface instead.

Let’s say the LocationUpdateListener I created above has multiple implementations. It is implemented in two activities namely ActivityA and ActivityB so I will create two functions in my module for their instantiation.

@Module
@InstallIn(ApplicationComponent::class)
abstract class LocationModule{

@ActivityALocationListener
@Binds
abstract fun locationListenerForActivityA(locationListenerImp:                                           ActivityA) : LocationUpdateListener

@ActivityBLocationListener
@Binds
abstract fun locationListenerforActivityB(locationListenerImp:                                           ActivityB) : LocationUpdateListener

}

To differentiate between them we will have to create two Qualifiers.

@Qualifier
annotation class ActivityALocationListener

@Qualifier
annotation class ActivityBLocationListener

Now whenever I want to inject this interface, I will tell Hilt which implementation to use by annotating the field with the corresponding qualifier.

@ActivityALocationListener
@Inject
lateinit var locationListener: LocationUpdateListener

Voila! We have injected all types of instances.

Note: Hilt provides two predefined qualifiers for Context namely @ApplicationContext and @ActivityContext. which can be used like this:

class MyAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: MyService
) { ... }

That’s it for this article. Hope it was helpful! If you like it, please hit like.

In the next article, I will go into the details of Hilt Components and their scopes.

Checkout the other articles of the Dependency Injection series:


Leave a comment