Injecting dependencies in classes not supported by Hilt (Hilt Part 4)


To inject dependencies in any class we have to add it to the Hilt tree. For fragments or activities we do it by adding the annotation @AndroidEntryPoint. Similarly we add @HiltViewModel to add a view model to the Hilt tree. Hilt doesn’t provide such annotations for all Android classes. There are some classes which Hilt doesn’t directly support like Content Providers, or Adapters.

So, how to inject dependencies in such classes?

If we want an Adapter to use Hilt to get some dependencies, we need to define an interface annotated with @EntryPoint. The interface should contain each binding type that we want and include qualifiers as well. Then add @InstallIn to specify the component in which to install the entry point.

Let’s take an example of a Adapter.

@EntryPoint
@InstallIn(SingletonComponent::class)
interface UtilitiesEntryPoint {
    var appUtils: AppUtils
    var firebaseUtils: FirebaseUtils
}

Now to access this entry point in our adapter, we will use a static method from EntryPointAccessors class.

class ProductsAdapter @Inject constructor(): ListAdapter {
   
    val appContext = MyApplication.getInstance().applicationContext
    val utilitiesEntryPoint =
               EntryPointAccessors.fromApplication(   
                        appContext, UtilitiesEntryPoint::class.java)

    val appUtils = utilitiesEntryPoint.appUtils()
    val firebaseUtils = utilitiesEntryPoint.firebaseUtils()   
}

Similarly, we can inject our entry point in an activity scope as well like this:

@EntryPoint
@InstallIn(ActivityComponent::class)
interface UtilitiesEntryPoint {
    var appUtils: AppUtils
    var firebaseUtils: FirebaseUtils
}
val utilitiesEntryPoint = EntryPointAccessors.fromActivity(activity,                         UtilitiesEntryPoint::class.java)

val appUtils = utilitiesEntryPoint.appUtils()
val firebaseUtils = utilitiesEntryPoint.firebaseUtils()

Point to note:

  1. When you create an @EntryPoint interface, it acts as a way to access the dependencies within a particular component’s scope. The parameter you pass to EntryPointAccessors.fromX() (where X can be Activity, Fragment, etc.) should be an instance of that component or an object annotated with @AndroidEntryPoint that holds the component.
  2. In the above example, UtilitiesEntryPoint is associated with the ActivityComponent. This means that you should pass an Activity (or an object annotated with @AndroidEntryPoint(Activity::class)) as a parameter to EntryPointAccessors.fromActivity(). The key is to ensure that the component you pass matches the component specified in the @InstallIn annotation on the @EntryPoint interface.

These points are important to ensure that you’re accessing the correct dependencies within the correct scope when using Hilt for dependency injection in your Android app. It helps maintain the proper lifecycle and scoping of the objects that Hilt manages.


Let’s see one more practical scenario where we will need @EntryPoint.

Let’s say I have a class NotificationBuilder which needs 2 dependencies to be injected in it using Hilt namely FirebaseUtils and NotificationDao

class NotificationBuilder @Inject constructor(){
    @Inject
    lateinit var firebaseUtils: FirebaseUtils
    @Inject
    lateinit var notificationDao: NotificationDao
}

class FirebaseUtils @Inject constructor(){
   ....
}

@Module
@InstallIn(SingletonComponent::class)
class DatabaseModule {

    @Singleton
    @Provides
    fun appDatabase(@ApplicationContext context: Context) =        AppDatabase.getDatabase(context)

    @Provides
    fun notificationDao(appDatabase: AppDatabase) = appDatabase.notificationDao()
}

We have told the system how to inject both our dependencies. FirebaseUtils instance can be created with a simple constructor and our Dao instance creation is specified using a Hilt Module.

So technically, when I use my NotificationBuilder class, both my dependencies should be injected. But they didn’t and we got a runtime exception that FirebaseUtils have not been initialized.

Why did this happen??

This happened because NotificationBuilder class wasn’t used in a class which was a part of the Hilt tree. Specifying @AndroidEntryPoint to a fragment or an activity tells the compiler this is the root node of the tree. But here we didn’t had a root node.
So in such cases, if we want to inject a dependency we will have to create an interface by using @EntryPoint annotation and then initialize our dependencies using it just like we did in the previous example.

@EntryPoint
@InstallIn(SingletonComponent::class)
interface NotificationUtilsEntryPoint {
    val notificationDao: NotificationDao
    val firebaseUtils: FirebaseUtils
}
class NotificationBuilder @Inject constructor(){

    lateinit var firebaseUtils: FirebaseUtils
    lateinit var notificationDao: NotificationDao
    init{
     val notificationFactory = EntryPointAccessors.fromApplication(
                     Farmer.getInstance().applicationContext,
                     NotificationUtilsEntryPoint::class.java)
     firebaseUtils = notificationFactory.firebaseUtils
     notificationDao = notificationFactory.notificationDao

In conclusion, Hilt provides a convenient way to handle dependency injection in Android using the @AndroidEntryPoint annotation. However, for classes that Hilt doesn’t directly support, we can create an entry point interface annotated with @EntryPoint and access dependencies using EntryPointAccessors. This approach allows us to inject dependencies effectively and maintain proper scoping.

That’s it for this article. Hope it was helpful! If you like it, please hit like. Don’t forget to visit the provided links to other articles in the series and show your appreciation.

In the next article, I’ll explain how to dynamically inject dependencies at runtime.

Thanks for reading!!.


Checkout the other articles of the Dependency Injection series:


Leave a comment