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:
- When you create an
@EntryPointinterface, it acts as a way to access the dependencies within a particular component’s scope. The parameter you pass toEntryPointAccessors.fromX()(where X can beActivity,Fragment, etc.) should be an instance of that component or an object annotated with@AndroidEntryPointthat holds the component. - In the above example,
UtilitiesEntryPointis associated with theActivityComponent. This means that you should pass an Activity (or an object annotated with@AndroidEntryPoint(Activity::class)) as a parameter toEntryPointAccessors.fromActivity(). The key is to ensure that the component you pass matches the component specified in the@InstallInannotation on the@EntryPointinterface.
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:
