androiddependency-injectionandroid-servicedagger-hilt

Injecting a repository into a Service in Android using Hilt


I have an Android project with Hilt dependency injection. I have defined MyApplication and MyModule as follows.

@HiltAndroidApp
class MyApplication : Application()

@Module
@InstallIn(ApplicationComponent::class)
abstract class MyModule {
    @Binds
    @Singleton
    abstract fun bindMyRepository(
        myRepositoryImpl: MyRepositoryImpl
    ): MyRepository
}

MyRepositoryImpl implements the MyRepository interface:

interface MyRepository {
    fun doSomething(): String
}

class MyRepositoryImpl
@Inject
constructor(

) : MyRepository {
    override fun doSomething() = ""
}

I can now inject this implementation of MyRepository into a ViewModel:

class MyActivityViewModel
@ViewModelInject
constructor(
    private val myRepository: MyRepository,
) : ViewModel() { }

This works as expected. However, if I try to inject the repository into a service, I get an error java.lang.Class<MyService> has no zero argument constructor:

class MyService
@Inject
constructor(
    private val myRepository: MyRepository,
): Service() { }

The same error occurs with an activity, too:

class MyActivity
@Inject
constructor(
    private val myRepository: MyRepository,
) : AppCompatActivity(R.layout.my_layout) { }

What am I doing wrong with the injection?


Solution

  • From the documentation on how we Inject dependencies into Android classes, we can learn the following:

    Hilt can provide dependencies to other Android classes that have the @AndroidEntryPoint annotation.

    Hilt currently supports the following Android classes:

    • Application (by using @HiltAndroidApp)
    • ViewModel (by using @HiltViewModel)
    • Activity
    • Fragment
    • View
    • Service
    • BroadcastReceiver

    So when you subclass any of these Android classes, you don't ask Hilt to inject dependencies through the constructors. Instead, you annotate it with @AndroidEntryPoint, and ask Hilt to inject its dependencies by annotating the property with @Inject:

    @AndroidEntryPoint
    class ExampleActivity : AppCompatActivity() { 
    
        @Inject
        lateinit var mAdapter: SomeAdapter 
    
        ...
    
    }
    

    So, in your case you should inject MyRepository in MyActivity and MyService like this:

    @AndroidEntryPoint
    class MyService: Service() {
    
        @Inject
        lateinit var myRepository: MyRepository
       
        ...
    
    }
    
    @AndroidEntryPoint
    class MyActivity: AppCompatActivity(R.layout.my_layout) { 
    
        @Inject
        lateinit var myRepository: MyRepository
    
        ...
    
    }
    

    And remember:

    Fields injected by Hilt cannot be private

    That's it for Android classes that is supported by Hilt.

    If you wonder what about classes not supported by Hilt (ex: ContentProvider)?! I recommend learning how from this tutorial @EntryPoint annotation on codelab (also don't forget to check the documentation for how to Inject dependencies in classes not supported by Hilt).