Dagger Hilt: Repository cannot be provided without an #Provides-annotated method - android

Hilt points out that this interface cannot be provided without an #Provides annotation:
interface PlannedListRepository {
fun getAllLists(): LiveData<List<PlannedList>>
suspend fun addList(plannedList: PlannedList)
suspend fun updateList(plannedList: PlannedList)
suspend fun deleteList(plannedList: PlannedList)
}
Implementation of the interface:
class PlannedListRepositoryImpl #Inject constructor(private val plannedListDao: PlannedListDao) :
PlannedListRepository {
...
}
As far as I know, if I want to get an interface, I can use #Binds to say to Dagger which implementation should be received. So I did this:
#Module
#InstallIn(ActivityComponent::class)
abstract class RepositoryModule {
#Binds
abstract fun providePlannedListRepository(impl: PlannedListRepositoryImpl) : PlannedListRepository
}
My ViewModel if it has something to deal with the error:
#HiltViewModel
class PlannedListViewModel #Inject constructor(
private val repository: PlannedListRepository
) : ViewModel() {
...
}
So what should I do to fix the error? The full error message:
AndroidStudioProjects\PlanShopping\app\build\generated\hilt\component_sources\debug\com\tetsoft\planshopping\PlannerApplication_HiltComponents.java:129: error: [Dagger/MissingBinding] com.tetsoft.planshopping.db.planned.PlannedListRepository cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements PlannerApplication_GeneratedInjector,
^
com.tetsoft.planshopping.db.planned.PlannedListRepository is injected at
com.tetsoft.planshopping.ui.planned.PlannedListViewModel(repository)
com.tetsoft.planshopping.ui.planned.PlannedListViewModel is injected at
com.tetsoft.planshopping.ui.planned.PlannedListViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.tetsoft.planshopping.PlannerApplication_HiltComponents.SingletonC ? com.tetsoft.planshopping.PlannerApplication_HiltComponents.ActivityRetainedC ? com.tetsoft.planshopping.PlannerApplication_HiltComponents.ViewModelC]
Here is the database module:
#Module
#InstallIn(SingletonComponent::class)
class DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context) : PlannerDatabase {
return Room.databaseBuilder(
appContext,
PlannerDatabase::class.java,
"PlannerDB"
)
.fallbackToDestructiveMigration()
.build()
}
#Provides
fun providePlannedListDao(plannerDatabase: PlannerDatabase) : PlannedListDao {
return plannerDatabase.plannedListDao()
}
#Provides
fun provideProductDao(plannerDatabase: PlannerDatabase) : ProductDao {
return plannerDatabase.productDao()
}
#Provides
fun provideSelectedProductDao(plannerDatabase: PlannerDatabase) : SelectedProductDao {
return plannerDatabase.selectedProductDao()
}
}

use #InstallIn(ViewModelComponent::class) in your repository module since you are injection repository in your view model

Is your application multi modules? If your answer is yes check your imports from your :app, in my case I facing the same error and I forgot to add my other modules where he DI was defined!

Better late than never.
Besides you need do the thing mentioned in the accepted answer.If you are working with compose or the latest Hilt version, init your ViewModel by the way of:
val viewModel: YourViewModel by viewModels()
do not use the way of:
#Inject lateinit var viewModel: YourViewModel

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 to inject a generic type

I have a repository interface that receives a generic type.
interface AuthRepo<R> {
val isUserAuthenticatedInFirebase: Boolean
suspend fun oneTapSignInWithGoogle(): R
suspend fun firebaseSignInWithGoogle(): R
}
The generic type is the class is BeginSignInResult that returns the result of the login. This class is not owned by me, so I have to inject it with a module. Here it is.
#Singleton
class AuthRepoImpl #Inject constructor(
...
#Named(SING_IN_RESULT)
private val singInResult: BeginSignInResult,
#Named(SING_UP_RESULT)
private val singUpResult: BeginSignInResult,
...
) : AuthRepo<BeginSignInResult>
This is the module where I am injecting the repository implementation.
#Module
#InstallIn(ViewModelComponent::class)
abstract class AuthModule {
#Binds
abstract fun provideAuthRepository(
authRepoImpl: AuthRepoImpl
): AuthRepo<BeginSignInResult>
}
The problem
As this class is not my property, I have to inject it with a module. This BeginSignInResult class also needs other classes that are also injected with modules. This is the code that injects it.
#Provides
#Named(AuthConstant.SING_IN_RESULT)
suspend fun provideSingInResult(
oneTapClient: SignInClient,
#Named(AuthConstant.SIGN_IN_REQUEST)
signUpRequest: BeginSignInRequest
) : BeginSignInResult {
return oneTapClient.beginSignIn(signUpRequest).await()
}
#Provides
#Named(AuthConstant.SING_UP_RESULT)
suspend fun provideSingUpResult(...) : BeginSignInResult ...
Hilt Error
This is the compile error. I think the use case that accesses the repository does not need to be shown
com...AuthRepo<com...BeginSignInResult> cannot be provided without an #Provides-annotated method.
com...AuthRepo<com..BeginSignInResult> is injected at
com...OneTapSingInUseCase(authRepo)
com...OneTapSingInUseCase<com...BeginSignInResult> is injected at
com...AuthRepo<com....BeginSignInResult> is injected at
com...OneTapSingInUseCase(authRepo)
com...OneTapSingInUseCase<com...BeginSignInResult> is injected at
com...LoginViewModel(�, oneTapUseCase)
com...LoginViewModel is injected at
com...LoginViewModel_HiltModules.BindsModule.binds(arg0)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com....App_HiltComponents.SingletonC ? com...App_HiltComponents.ActivityRetainedC ? com...App_HiltComponents.ViewModelC]

How do you inject a specific subclass based on the which class is instantiated with Hilt?

I have these Repositories dependent on DataSources.
class LocationRepository: Repository<String>(LocationDataSource())
class ItemRepository: Repository<String>(ItemDataSource())
I would like to inject the Repository class with Hilt like this to prevent code duplication.
abstract class Repository<T> {
#Inject lateinit var dataSource: DataSource<T>
...
}
I have tried this, but am not sure how to get Hilt to use the right ones.
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class ItemDataSourceAnnotation
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class LocationDataSourceAnnotation
#Module
#InstallIn(SingletonComponent::class)
object DataSourceModule {
#ItemDataSourceAnnotation
#Provides
#Singleton
fun provideItemDataSource(): DataSource{
return ItemDataSource()
}
#LocationDataSourceAnnotation
#Provides
#Singleton
fun provideLocationDataSource(): DataSource{
return LocationDataSource()
}
}
#Module
#InstallIn(SingletonComponent::class)
object RepositoryModule {
#Provides
#Singleton
fun providesItemRepository(
#ItemDataSourceAnnotation itemDataSource: ItemDataSource
): ItemRepository {
return ItemRepository()
}
#Provides
#Singleton
fun providesLocationRepository(
#LocationDataSourceAnnotation locationDataSource: LocationDataSource
): LocationRepository {
return LocationRepository()
}
}
If what you want is to avoid having the data source field in each repository subclass, you could add a data source type parameter and val to Repository:
class Repository<ValueT, DataSourceT>(val dataSource: DataSourceT) {
...
}
class LocationRepository: Repository<Location, LocationDataSource> #Inject constructor(dataSource: LocationDataSource): super(dataSource) {
...
}
It's better to use constructor injection than field injection, and using constructor injection, you have to pass the constructor parameters through the subclasses -- you can't do it only in the base class.

Android Hilt dagger inject interface in viewModel #ViewModelInject got UninitializedPropertyAccessException

I trying on Hilt codelab
https://codelabs.developers.google.com/codelabs/android-hilt#10
It's working fine with Activity and Fragment
logger is a RoomDB
Then I try to inject logger into viewModel with this article
By add
implementation "androidx.hilt:hilt-lifecycle-viewmodel
:1.0.0-alpha02"
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
ViewModelCode
class RecordFragmentViewModel #ViewModelInject constructor(#Assisted private val savedStateHandle: SavedStateHandle) :
ViewModel() {
#DatabaseLogger
#Inject
lateinit var logger: LoggerDataSource
Class logger to inject
class LoggerLocalDataSource
#Inject constructor(private val logDao: LogDao) : LoggerDataSource {
LoggingModule
#Qualifier
annotation class InMemoryLogger
#Qualifier
annotation class DatabaseLogger
#InstallIn(ApplicationComponent::class)
#Module
abstract class LoggingDatabaseModule {
#DatabaseLogger
#Singleton
#Binds
abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}
#InstallIn(ActivityComponent::class)
#Module
abstract class LoggingInMemoryModule {
#InMemoryLogger
#ActivityScoped
#Binds
abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}
DatabaseModule
#InstallIn(ApplicationComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
It's compile and run without error.
However, I use debug to watch logger and its got.
Method threw 'kotlin.UninitializedPropertyAccessException' exception.
I call logger.something() at run time its throw
Fatal Exception: kotlin.UninitializedPropertyAccessException
lateinit property logger has not been initialized
More info
https://dagger.dev/hilt/migration-guide.html
https://codelabs.developers.google.com/codelabs/android-hilt#10
https://medium.com/mobile-app-development-publication/injecting-viewmodel-with-dagger-hilt-54ca2e433865
Since LoggerDataSource is a interface we need to specify which implementation we need to inject. Thanks to #Andrew for the idea of inject to constructor
class RecordFragmentViewModel
#ViewModelInject
constructor(#Assisted private val savedStateHandle: SavedStateHandle,
#DatabaseLogger private val logger: LoggerDataSource) :
ViewModel(), LifecycleObserver {
To specify
#Qualifier
annotation class InMemoryLogger
#Qualifier
annotation class DatabaseLogger
#InstallIn(ApplicationComponent::class)
#Module
abstract class LoggingDatabaseModule {
#DatabaseLogger
#Singleton
#Binds
abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}
#InstallIn(ActivityComponent::class)
#Module
abstract class LoggingInMemoryModule {
#InMemoryLogger
#ActivityScoped
#Binds
abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

Gradle build keeps failing due to MissingBinding on Dagger Hilt migration

I'm trying to migrate my project to Dagger Hilt and facing an issue with missing binding. I was following the Googles codelab to achieve this.
This is the place where the build fails:
error: [Dagger/MissingBinding] java.util.Map<java.lang.String,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?>>> cannot be provided without an #Provides-annotated method.
public abstract static class ApplicationC implements WhatToCookApp_GeneratedInjector,
^
java.util.Map<java.lang.String,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?>>> is injected at
dagger.android.DispatchingAndroidInjector(�, injectorFactoriesWithStringKeys)
dagger.android.DispatchingAndroidInjector<java.lang.Object> is injected at
dagger.android.support.DaggerAppCompatActivity.androidInjector
at.bwappsandmore.whattocook.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [at.bwappsandmore.whattocook.WhatToCookApp_HiltComponents.ApplicationC ? at.bwappsandmore.whattocook.di.ActivityModule_InjectMainActivity.MainActivitySubcomponent]
It is also requested at:
dagger.android.DispatchingAndroidInjector(�, injectorFactoriesWithStringKeys)
The following other entry points also depend on it:
These are the relevant parts of the project:
#HiltAndroidApp
open class WhatToCookApp : Application() {
val appComponent: AppComponent by lazy {
initializeComponent()
}
open fun initializeComponent(): AppComponent {
return DaggerAppComponent.factory().create(applicationContext)
}
}
The AppComponent:
#Singleton
#Component(
modules = [AppModule::class,
ActivityModule::class,
AndroidSupportInjectionModule::class]
)
interface AppComponent : AndroidInjector<WhatToCookApp> {
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
The AppModule:
#InstallIn(ApplicationComponent::class)
#Module
class AppModule {
#Provides
fun provideDB(#ApplicationContext context: Context): AppDatabase {
return AppDatabase.getDatabase(context)
}
#Provides
fun provideDAO(app: AppDatabase): WhatToCookDao {
return app.whatToCookDao()
}
#Provides
fun provideAppRepository(dao: WhatToCookDao): AppRepository{
return AppRepository(dao)
}
#Provides
fun provideSharedPreferences(#ApplicationContext context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}
}
The ApplicationModulde:
#InstallIn(ApplicationComponent::class)
#Module
interface ActivityModule {
#ActivityScope
#ContributesAndroidInjector(modules = [ViewModelModule::class])
fun injectMainActivity(): MainActivity
}
The ViewModelModule:
#InstallIn(ApplicationComponent::class)
#Module
abstract class ViewModelModule {
companion object{
#Provides
fun providesSharedViewModel (activity: MainActivity) : SharedViewModel = activity.viewModel
}
}
The ActivityScope:
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
I realise that I have to use the #Provides annotation for the AndroidInjector, but I don't know where and how. Any help is appreciated.
Thank you so much in advance.

Categories

Resources