Injecting a repository into a Service in Android using Hilt - android

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?

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

Also If you overriding onCreate in your Service - don't forget to add super.onCreate() otherwise you will get runtime exception that the myRepository value cannot be initialised (I was struggling with this error for some time during refactoring my service so maybe it will be helpful for somebody)
#AndroidEntryPoint
class MyService : Service() {
#Inject
lateinit var myRepository: MyRepository
override fun onCreate() {
super.onCreate()
}
}

Your use of #Inject on the MyService class is as if MyService is to be injected at some other location.
If I understand correctly, you want something more akin to:
#AndroidEntryPoint
class MyService : Service() {
#Inject
lateinit var myRepository: MyRepository
}

Related

Inject an instance of repository class into Application class which is provided by Module with ViewModelScope

I am having a problem that I need inject an instance of repository class into Application class which is provided by Module (Installed in ViewModelComponent, and provide function marked with #ViewModelScope annotation)
Repository
interface IARepository
class ARepository #Inject constructor() : IARepository
Module
#Module
#InstallIn(ViewModelComponent::class)
interface RepositoryModule {
#Binds
#ViewModelScoped
fun provideARepos(impl: ARepository): IARepository
}
ViewModel
#HiltViewModel
class TestViewModel #Inject constructor(
private val useCase1: UseCase1,
private val useCase2: UseCase2,
) {
...
}
Two UseCase1 and UseCase2 are using IARepository, since if I provides IARepository with ViewModelScope, two instance useCase1 and useCase2 will be using the same instance of repository.
It worked until I inject repository into Application (singleton things)
Application
#HiltAndroidApp
class TestApplication : Application() {
#Inject
lateinit var a: IARepository
}
After that I got error
[Dagger/MissingBinding] IARepository cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint
Application_HiltComponents.java:129: error: [Dagger/MissingBinding] ...core.domain.IARepository cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
A binding for ....core.domain.IARepository exists in ...Application_HiltComponents.ViewModelC:
....core.domain.IARepository is injected at
[...Application_HiltComponents.SingletonC] ...Application.a
...Application is injected at
...Application_HiltComponents.SingletonC] ...Application_GeneratedInjector.injectMoonRoverApplication
In application, I tried switch to inject directly implementation class is ARepository, it worked fine.
#HiltAndroidApp
class TestApplication : Application() {
#Inject
lateinit var a: ARepository
}
But I still want to use interface. Are there any solution for it?
I think you have to use #Provides in module as below
#Module
#InstallIn(ViewModelComponent::class)
interface RepositoryModule {
#Binds
#ViewModelScoped
#Provides
fun provideARepos(impl: ARepository): IARepository
}
Also add #HiltViewModel in your view model as below
#HiltViewModel
class TestViewModel #Inject constructor(
private val useCase1: UseCase1, private val useCase2: UseCase2
) {
...
}
I hope it will help you.
in viewmodel please specify the #HiltViewModel
#HiltViewModel
class TestViewModel #Inject constructor(
private val useCase1: UseCase1, private val useCase2: UseCase2
) {
...
}
edited:-
#Module
#InstallIn(SingletonComponent::class)
object Module {
#Provides
fun ProvideImplRepo() = ImplRepo()
}
#Module
#InstallIn(ViewModelComponent::class)
abstract class RepositoryModule {
#Binds
abstract fun bindLoginRepository(impl: ImplRepo): Repo
}

How can I dependency injection Context into ViewModel using Hilt in Android Studio?

Before, I use Code A to pass Context to ViewModel.
Now I hope to use Hilt as dependency injection to pass Context,
I have read the article , and Code B is from the article.
1: Is the Code B correct way to pass Context into ViewModel?
2: In my mind, in order to use Hilt in Android Studio project, I have added such as the Code C in project, do I need to use fun provideApplicationContext() = MyApplication() in Code B?
Code A
class HomeViewModel(private val mApplication: Application, val mRepository: DBRepository) : AndroidViewModel(mApplication) {
...
}
Code B
class MainViewModel #ViewModelInject constructor(
#ApplicationContext private val context: Context,
private val repository: Repository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
#Singleton
#Provides
fun provideApplicationContext() = MyApplication()
Code C
#HiltAndroidApp
class MyApplication : Application() {
}
This is how I injected applicationContext in the viewmodel and it worked fine.
Base Application
#HiltAndroidApp
class BaseApplication: Application()
App Module
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun provideApplication(#ApplicationContext app: Context): BaseApplication{
return app as BaseApplication
}
View Model
#HiltViewModel
class PendingListViewModel
#Inject
constructor(private val application: BaseApplication)
Usage In the ViewModel
AppCompatResources.getDrawable(application.applicationContext, R.drawable.marker_circle)

Hilt field injection in the super Fragment or ViewModel

I'm using Dagger-Hilt for dependency injection in my Android project, now I have this situation where I have a base abstract Fragment
BaseViewModel.kt
abstract class BaseViewModel constructor(
val api: FakeApi,
) : ViewModel() {
//...
}
Here, I have a dependency which is FakeApi. What I'm trying to do is to inject the FakeApi into the BaseViewModel to be available in the BaseViewModel and all its children.
The first approach I tried is using the constructor injection and inject it to the child and pass it to the super using the constructor.
TaskViewModel.kt
#HiltViewModel
class TaskViewModel #Inject constructor(
api: FakeApi
) : BaseViewModel(api){
}
This approach works fine, but I don't need to pass the dependency from the child to the super class, I need the FakeApi to be automatically injected in the BaseViewModel without having to pass it as I have three levels of abstraction (There is another class inheriting from the TaskViewModel) So I have to pass it two times.
The second approach was to use the field injection as follows
BaseViewModel.kt
abstract class BaseViewModel: ViewModel() {
#Inject
lateinit var api: FakeApi
//...
}
TaskViewModel.kt
#HiltViewModel
class TaskViewModel #Inject constructor(): BaseViewModel() {
}
This approach didn't work for me and the FakeApi wasn't injected and I've got an Exception
kotlin.UninitializedPropertyAccessException: lateinit property api has not been initialized
My questions are
Why field injection doesn't work for me?
Is there any way to use constructor injection for the super class instead of passing the dependency from the child?
Thanks to this Github Issue I figured out that the problem is that you can't use the field injected properties during the ViewModel constructor initialization, but you still use it after the constructor -including all the properties direct initialization- has been initialized.
Dagger firstly completes the constructor injection process then the field injection process takes place. that's why you can't use the field injection before the constructor injection is completed.
❌ Wrong use
abstract class BaseViewModel : ViewModel() {
#Inject
protected lateinit var fakeApi: FakeApi
val temp = fakeApi.doSomething() // Don't use it in direct property declaration
init {
fakeApi.doSomething() // Don't use it in the init block
}
}
✔️ Right use
abstract class BaseViewModel : ViewModel() {
#Inject
protected lateinit var fakeApi: FakeApi
val temp: Any
get() = fakeApi.doSomething() // Use property getter
fun doSomething(){
fakeApi.doSomething() // Use it after constructor initialization
}
}
Or you can use the by lazy to declare your properties.
I tested and I see that field injection in base class still work with Hilt 2.35. I can not get the error like you so maybe you can try to change the Hilt version or check how you provide FakeApi
abstract class BaseViewModel : ViewModel() {
#Inject
protected lateinit var fakeApi: FakeApi
}
FakeApi
// Inject constructor also working
class FakeApi {
fun doSomeThing() {
Log.i("TAG", "do something")
}
}
MainViewModel
#HiltViewModel
class MainViewModel #Inject constructor() : BaseViewModel() {
// from activity, when I call this function, the logcat print normally
fun doSomeThing() {
fakeApi.doSomeThing()
}
}
AppModule
#Module
#InstallIn(SingletonComponent::class)
class AppModule {
#Provides
fun provideAPI(
): FakeApi {
return FakeApi()
}
}
https://github.com/PhanVanLinh/AndroidHiltInjectInBaseClass
After many searches on the Internet, I think the best solution is to not use initializer blocks init { ... } on the ViewModel, and instead create a function fun initialize() { ... } that will be called on the Fragment.
BaseViewModel.kt
#HiltViewModel
open class BaseViewModel #Inject constructor() : ViewModel() {
#Inject
protected lateinit var localUserRepository: LocalUserRepository
}
OnboardingViewModel.kt
#HiltViewModel
class OnboardingViewModel #Inject constructor() : BaseViewModel() {
// Warning: don't use "init {}", the app will crash because of BaseViewModel
// injected properties not initialized
fun initialize() {
if (localUserRepository.isLoggedIn()) {
navigateToHomeScreen()
}
}
}
OnBoardingFragment.kt
#AndroidEntryPoint
class OnBoardingFragment() {
override val viewModel: OnboardingViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.initialize()
}
}
Sources:
https://github.com/google/dagger/issues/2507
the answers on this question

android hilt Inject into ViewModel

i am try to inject a module to MyViewModel
here is my Module
#Module
#InstallIn(ViewModelComponent::class)
object EngineModule {
#Provides
fun getEngine(): String = "F35 Engine"
}
and this my viewModel
#HiltViewModel
class MyViewModel #Inject constructor(): ViewModel() {
#Inject lateinit var getEngine: String
fun getEngineNameFromViewModel(): String = getEngineName()
}
and it throws
kotlin.UninitializedPropertyAccessException: lateinit property getEngine
has not been initialized
however if i change ViewModelComponent::class to ActivityComponent::class and inject like this
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject
lateinit var getEngine: String
it works perfectly
any idea how to inject viewModels?
Also you can just remove #Inject constructor since you are already providing the dependency using dagger module:
#HiltViewModel
class MyViewModel (private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
So, Basically you can either provide the dependency using dagger module or constructor injection.
Since required dependency is going to be injected in the ViewModel's constructor, you just need to modify your code in the following way to make it work:
#HiltViewModel
class MyViewModel #Inject constructor(private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}

Hilt ViewModel Injection

I've been trying to resolve my issue but I can't find anything...
MyApp.kt :
#HiltAndroidApp
class MyApp: Application()
MainActivity.kt
#AndroidEntryPoint
class MainActivity: AppCompatActivity()
MyFragment.kt
class MyFragment: Fragment() {
private val myViewModel: MyViewModel by viewModels()
}
MyViewModel.kt
class MyViewModel #ViewModelInject constructor(
application: Application,
myRepository: MyRepository, {*}
#Assisted private val state: SavedStateHandle,
): AndroidViewModel(application)
MyRepository.kt
interface MyRepository {
fun test()
}
class MyRepositoryImpl #Inject constructor(): MyRepository {
override fun test() { print("") }
}
MyModule.kt
#Module
#InstallIn(SingletonComponent::class)
abstract class MyModule {
#Binds
abstract fun bindsMyRepository(impl: MyRepositoryImpl): MyRepository
}
If I comment the line with "{*}", it works fine, but since I try to add my custom repository, I got this error :
java.lang.RuntimeException: Cannot create an instance of class MyViewModel
Caused by: java.lang.NoSuchMethodException: [class android.app.Application]
Do you have any idea of what I am missing?
Thanks you!
Complementary informations:
I forgot to mention that this is a multi module app.
In the first module, there is the basic app : MainActivity, a manifest (1)
In the second module, there is the core app : MyApp, a manifest (2)
In the third module, there is the module : MyFragment, MyViewModel, MyRepository and MyModule, a manifest (3)
Manifests are like this:
(1)
<manifest...>
<application...
android:name=".secondModule.MyApp">
</manifest>
(2)
<manifest...>
<application>
<provider...></provider>
</application>
</manifest>
(3)
<manifest.../>
First Annotate your fragment
#AndroidEntryPoint
class MyFragment: Fragment() {
private val myViewModel: MyViewModel by viewModels()
}
Secondly you have to extend ViewModel instead of AndroidViewModel
class MyViewModel #ViewModelInject constructor(
#Application context : Context ,
myRepository: MyRepository, {*}
#Assisted private val state: SavedStateHandle,
): ViewModel()
Subsitute SingletonComponent With ApplicationComponent
#Module
#InstallIn(ApplicationComponent::class)
abstract class MyModule {
#Binds
abstract fun bindsMyRepository(impl: MyRepositoryImpl): MyRepository
}
PS : Don't forget to add your application class( MyApp ) to your manifest file
You must use #AndroidEntryPoint for your fragments
#AndroidEntryPoint
class MyFragment: Fragment() {
private val myViewModel: MyViewModel by viewModels()
}
Also ApplicationComponent was removed in Dagger 2.30, but it is used in androidx.hilt:hilt (#ViewModelInject).
They added ApplicationComponent back in 2.31.1 because of the library mentioned above.
Update your Dagger version to 2.31.1 (use SingletonComponent instead of ApplicationComponent) or use new feature Hilt View Models
#HiltViewModel
class MyViewModel #Inject constructor(
private val application: Application,
myRepository: MyRepository,
private val state: SavedStateHandle,
): ViewModel()

Categories

Resources