If I just use the AuthRepository class in a single UseCase it's fine. However, if I try to use it in both AuthUseCase and RefreshTokenUseCase as in the example, I get an error.
Any suggestions other than using Lazy<> ?
Any help will be appreciated.
-
Error
-
App_HiltComponents.java:139: error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract static class SingletonC implements App_GeneratedInjector,
^
AppRepository is injected at
RefreshTokenTokenUseCase(authRepository)
RefreshTokenTokenUseCase is injected at
AppAuthenticator(refreshTokenTokenUseCase)
......
...
..
AuthUseCase(authRepository)
AuthUseCase is injected at
MainViewModel(authUseCase, …)
MainViewModel is injected at
MainViewModel_HiltModules.BindsModule.binds(vm)
My Code
-
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Singleton
#Provides
fun provideRetrofit(): Retrofit =
Retrofit.Builder()
.baseUrl(Data.url)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Singleton
#Provides
fun provideAuthAPI(
retrofit: Retrofit
): AuthAPI = retrofit.create(AuthAPI::class.java)
}
#Singleton
class AuthRepository #Inject constructor(
private var authAPI: AuthAPI,
) {
}
#Singleton
class AuthUseCase #Inject constructor(
private val authRepository: AuthRepository
) : UseCase<Response?, AuthUseCase.Params>() {
}
#Singleton
class RefreshTokenUseCase #Inject constructor(
private val authRepository: AuthRepository
) : UseCase<String?, RefreshTokenUseCase.Params>() {
}
You can use Provider<T> instead of Lazy and than call .get() on it.
#Singleton
class RefreshTokenUseCase #Inject constructor(
private val authRepositoryProvider: Provider<AuthRepository>
) : UseCase<String?, RefreshTokenUseCase.Params>() {
fun getRefreshToken() = authRepositoryProvider.get().getRefreshToken() //example of usage
}
This means RefreshTokenUseCase will be created before AuthRepository is created and later on it will receive singleton AuthRepository instance it needs.
For more complete explanation check this SO post.
Related
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
}
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
I have implemented Hilt on my app. I am getting this error for the first time. Does anyone have an idea how to go about it?
weatherstackapp/utils/BaseApplication_HiltComponents.java:128: error:
[Dagger/MissingBinding]
com.malinikali.weatherstackapp.utils.BaseApplication cannot be provided
without an #Inject constructor or an #Provides-annotated method.
public abstract static class SingletonC implements
BaseApplication_GeneratedInjector,
^
Below is my BaseApplication class
#HiltAndroidApp
class BaseApplication : Application()
Below is my AppModule class
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
private val client = OkHttpClient.Builder().apply {
addInterceptor(ApiInterceptor())
}.build()
#Singleton
#Provides
fun provideContext(application: BaseApplication): Context {
return application.applicationContext
}
#Provides
fun providesBaseUrl() = Constants.BASE_URL
#Provides
#Singleton
fun provideRetrofitInstance(BASE_URL:String):ApiService =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(client)
.build()
.create(ApiService::class.java)
}
Hilt injects Application and #ApplicationContext Context by default. If this is the only binding that uses BaseApplication, you can simply change the parameter to one of these default bindings instead:
#Singleton
#Provides
fun provideContext(application: Application): Context {
return application.applicationContext
}
If you really need a BaseApplication binding, you can create a #Provides method based on one of the included bindings:
#Provides
fun provideBaseApplication(application: Application): BaseApplication {
return application as BaseApplication
}
I've been trying to resolve my issue but I can't find anything...
MyAppApplication.kt :
#HiltAndroidApp
class MyAppApplication : Application()
MainActivity.kt
#AndroidEntryPoint
class MainActivity: AppCompatActivity()
MyViewModel.kt
#HiltViewModel
class MyViewModel #Inject constructor(private val myRepository: MyRepository,
private val appPreference: AppPreference) : ViewModel(){}
MyRepository.kt
interface MyRepository {
fun test()
}
class MyRepositoryImpl #Inject constructor(private val service:Service): MyRepository {
override fun test() { print("") }
}
HiltModule.kt
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
private const val BASE_URL = "..."
#Singleton
#Provides
fun getGsonConverterFactory(): GsonConverterFactory = GsonConverterFactory.create(
GsonBuilder()
.setLenient()
.create()
)
#Singleton
#Provides
fun getRetrofit(gsonConverterFactory: GsonConverterFactory): Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(gsonConverterFactory)
.build()
#Singleton
#Provides
fun provideService(retrofitClient: Retrofit) = retrofitClient.create(Service::class.java)
#Singleton
#Provides
fun getAppPreference(application: Application) :AppPreference = AppPreferenceImpl(application)
}
#Module
#InstallIn(ViewModelComponent::class)
object RepositoryModule {
#ViewModelScoped
#Provides
fun getMyRepository(myRepository: MyRepositoryImpl): MyRepository = myRepository
}
failed with 2 errors
[Dagger/MissingBinding] com.example.app.repository.MyRepositoryImpl cannot be provided without an #Inject constructor or an #Provides-annotated method.
com.example.myapp.repository.MyRepositoryImmpl is injected at....
java.lang.reflect.InvocationTargetException (no error message)
Do you have any idea of what I am missing?
Thanks you!
Complementary informations:
I have tried switching ViewModelScope to Singleton.
I have tried switching Object RepositoryModule to
#Module
#InstallIn(ViewModelComponent::class)
abstract class RepositoryModule {
#Binds
#ViewModelScoped
abstract fun bindMyRepository(myRepository: MyRepositoryImpl): MyRepository
}
I have tried having them all inside One HiltModule Object
I have tried
#Module
#InstallIn(SingletonComponent::class)
object RepositoryModule {
#Provides
fun getMyRepository(service: Service): MyRepository = MyRepositoryImpl(service)
}
But same issue mentioned.
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
}