Dagger 2 MissingBinding when swapping concretion for interface - android

I have two classes that I'm able to have Dagger find and inject for me to use successfully:
TrackEvent
class TrackEvent #Inject constructor(
private val getTrackingProperties: SomeClass
) : UseCase<Boolean, TrackingEvent> {
override suspend operator fun invoke(params: TrackingEvent): Boolean {
return true
}
SomeClass (note: used as a dependency in TrackEvent)
class SomeClass #Inject constructor() {
override suspend operator fun invoke(): UserTrackingPropertiesResult {
return UserTrackingPropertiesResult()
}
}
TrackEvent has an entry in an #Module annotated interface because it's an implementation of the UseCase interface:
#Component(modules = [MyModule::class])
interface ShiftsComponent {
fun inject(homeFragment: HomeFragment)
}
#Module
interface MyModule {
#Binds
fun bindsTrackEventUseCase(useCase: TrackEvent): UseCase<Boolean, TrackingEvent>
}
Use Case interfaces
interface UseCase<out T, in P> {
suspend operator fun invoke(params: P): T
}
interface NoParamUseCase<out T> {
suspend operator fun invoke(): T
}
What I'd like to do is to inject an interface into TrackEvent instead of the concrete SomeClass. So I make SomeClass implement a NoParamUseCase
class SomeClass #Inject constructor(): NoParamUseCase<UserTrackingPropertiesResult> {
override suspend operator fun invoke(): UserTrackingPropertiesResult {
return UserTrackingPropertiesResult()
}
}
update TrackEvent to inject the interface:
class TrackEvent #Inject constructor(
private val getTrackingProperties: NoParamUseCase<UserTrackingPropertiesResult>) : UseCase<Boolean, TrackingEvent> {
override suspend operator fun invoke(params: TrackingEvent): Boolean {
return true
}
}
…and update MyModule to inform Dagger of which implementation I'd like to use:
#Module
interface MyModule {
#Binds
fun bindsTrackEventUseCase(useCase: TrackEvent): UseCase<Boolean, TrackingEvent>
// New
#Binds
fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<UserTrackingPropertiesResult>
}
Dagger now claims that there is a missing binding and that I need to declare an #Provides annotated method:
error: [Dagger/MissingBinding] com.myapp.core.domain.usecase.NoParamUseCase<? extends com.myapp.core.tracking.UserTrackingPropertiesResult> cannot be provided without an #Provides-annotated method.
public abstract interface MyComponent {
^
com.myapp.core.domain.usecase.NoParamUseCase<? extends com.myapp.core.tracking.UserTrackingPropertiesResult> is injected at
com.myapp.tasks.tracking.domain.usecase.TrackEvent(getTrackingProperties, …)
…
As far as I can tell, this isn't true:
While, I've opted for #Binds in this instance, replacing this with #Provides and manually providing dependencies here yields the same error.
I'm using the exact same approach for the TrackEvent class and this works.
The only thing I've changed is that I'd like to provide an interface instead. I'd fully understand this error had I not provided the #Binds declaration.
This is different to this question as there's no ambiguity as to which implementation I'm asking Dagger to use in the way that there would be if I had two or more implementations of the same interface.
Why would I get this error now?

According to dagger error message, it expects covariant type NoParamUseCase<? extends UserTrackingPropertiesResult>, but DI module provides invariant NoParamUseCase<UserTrackingPropertiesResult>. To generate appropriate signature for provide method you can change it like this:
#Binds fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<#JvmWildcard UserTrackingPropertiesResult>
After that your code should be compiled successfully.

Related

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]

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

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

Dagger 2 Unable to provide and inject Interface and its implementation in android

#Module
abstract class PersonUsecaseModule{
#Provides
internal fun provideUseCase(useCase: GetPersonUseCaseImpl): PersonUseCase = useCase
#Provides
internal fun provideMutableLiveData() = MutableLiveData<PersonUseCase.Result>()
#Provides
internal fun providePersonWidgetImplScreen(widget: PersonWidgetImpl): PersonWidget = widget
}
this is my module class and i am injecting it in MainActivity i am getting error
error: com.anil.gorestapp.person.injection.PersonUsecaseModule is abstract and has instance #Provides methods. Consider making the methods static or including a non-abstract subclass of the module instead.
public abstract interface ApplicationComponent {
I don't know why i am getting this Error Please help me what i am doing mistake
lateinit var personWidget: PersonWidget
AppLication component :
#Singleton
#Component(
modules = [
ApplicationModule::class,
ActivityModule::class,
NetworkModule::class
]
)
interface ApplicationComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
fun inject(application: MainApplication)
}
ActivityModule
abstract class ActivityModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
#Binds
abstract fun bindSharedPreferences(appPreferenceImpl: AppPreferenceImpl): AppPreference
}
person module
abstract class ActivityModule {
#ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
#Binds
abstract fun bindSharedPreferences(appPreferenceImpl: AppPreferenceImpl): AppPreference
}
So the issue is, You have declared your module as "abstract" and along with this, you are also using #Provides annotation on methods which are returning implementation of interface.
Dagger doesn't allow that. You can fix this issue in two way:
First Way: Remove abstract from your module like this:
#Module
class ActivityModule {
#Provides
fun providePersonWidget(personWidget: PersonWidgetImpl) : PersonWidget = personWidget
}
Second way: Use #Bind annotation on method instead of provide like this
#Module
abstract class ActivityModule {
#Binds
abstract fun providePersonWidget(personWidget: PersonWidgetImpl) : PersonWidget
}
Note: In second way you can declare your method and class as abstract but cannot return anything.
If you are still not very clear with my answer, you can refer this branch which I have created for you.
https://github.com/parmeshtoyou/StackOverflow/compare/sof_23_oct_21_dagger_issue?expand=1

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent

Android - Dagger injection at runtime

i need to inject the class at runtime using dagger. what my problem is am getting compile time error while injecting the class locally in method and also i was not able to inject at runtime without using constant for #Named
Example
interface PerformActionInterface{
fun performAction()
}
class P1 : PerformActionInterface{
override fun performAction(){
}
}
class P2 : PerformActionInterface{
override fun performAction(){
}
}
class PerformAction #Inject constructor(){
fun perform(name : String){
#Inject
#Named(name)
performActionInterface : PerformActionInterface
performActionInterface.performAction()
}
}
were as in dagger implementation i will be doing like this
#Binds
#Named("p1")
abstract bindP1Class(p1 : P1) :PerformActionInterface
#Binds
#Named("p2")
abstract bindP1Class(p2 : P2) :PerformActionInterface
Any help for how to inject this at runtime ?
You can't annotate something in runtime, the element value in Java annotation has to be a constant expression.
But this use case can be solved by map multibind.
In your Module, in addition to just #Bind or #Provide, also annotate the abstract fun with #IntoMap and the map key (sorry for any error in my Kotlin)
#Binds
#IntoMap
#StringKey("p1")
abstract fun bindP1Class(p1: P1): PerformActionInterface
#Binds
#IntoMap
#StringKey("p2")
abstract fun bindP2Class(p2: P2): PerformActionInterface
Then in your PerformAction class, declare a dependency of a map from String to PerformActionInterface, and do whatever with the map:
// map value type can be Lazy<> or Provider<> as needed
class PerformAction #Inject constructor(
val map: Map<String, #JvmSuppressWildcards PerformActionInterface>) {
fun perform(name: String) {
map.get(name)?.performAction()
// or do something if the get returns null
}
}

Categories

Resources