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.
Related
I have two modules one for ViewModelModule Providers and another for Application level which contains retrofit,intent. So before passing to the component I have included the ViewModelModule to the ApplicationModule like this
#Module(includes = [ViewModelModule::class])
class ApplicationModule {
And My component interface is Like:
#Singleton
#Component(modules = [ApplicationModule::class, ContextModule::class])
interface AppComponent {
fun inject(activity: LoginActivity)
fun inject(activity: RegisterActivity)
fun inject(activity: SplashActivity)
}
ApplicationModule class
#Module(includes = [ViewModelModule::class])
class ApplicationModule {
#Singleton
#Named("GotoLogin")
#Provides
fun provideSplashIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, LoginActivity::class.java)
}
#Singleton
#Named("GotoDashboard")
#Provides
fun provideLoginIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, DashboardActivity::class.java)
}
#Singleton
#Named("GotoRegister")
#Provides
fun provideRegisterIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, RegisterActivity::class.java)
}
#Singleton
#Provides
fun provideTimer(): Timer {
return Timer()
}
}
ViewModelModule class
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindviewmodelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(RegisterViewModel::class)
abstract fun bindRegisterViewModel(registerViewModel: RegisterViewModel): ViewModel
}
Scenario:
Suppose when I trying to use a function from ApplicationModule Class in LoginActivity then the Error is coming.
Usage:
class LoginActivity : AppCompatActivity() {
#Inject
lateinit var factory: ViewModelProvider.Factory
lateinit var loginViewModel: LoginViewModel
lateinit var context: Context
#Named("GotoRegister")
#Inject
lateinit var regiseterIntent: Intent
But when I am trying to use anything from the ApplicationModule class a compile error is coming
Crony\app\build\tmp\kapt3\stubs\debug\com\app\crony\di\AppComponent.java:8: error: [Dagger/MissingBinding] androidx.appcompat.app.AppCompatActivity cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface AppComponent {
^
androidx.appcompat.app.AppCompatActivity is injected at
com.app.crony.di.ApplicationModule.provideRegisterIntent(appCompatActivity)
#javax.inject.Named("GotoRegister") android.content.Intent is injected at
com.app.crony.LoginActivity.regiseterIntent
Full Source Code:
Github Link
I can feel that I am missing something but not able to sort out the issue.
Replace activity with context. It will work well.
I am using dagger2 and kotlin in my project. I have injected activity and viewmodels and now I want to inject appwidgetprovider class for app widgets. I can`t find a way to inject fields in to appwidgetprovider class. Here is my dagger2 implementaion.
this is App Component class
#Singleton
#Component(
modules = [
UserInformationModule::class,
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::class,
ServiceBuilderModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(application: BaseClass)
}
This is AppModule class
#Module(includes = [ViewModelModule::class, CoreDataModule::class])
class AppModule {
#Singleton
#Provides
fun provideMyMyAppService(
#MyAppAPI okHttpClient: OkHttpClient,
converterFactory: MoshiConverterFactory
) = provideService(okHttpClient, converterFactory, MyMyAppApi::class.java)
#MyAppAPI
#Provides
fun providePrivateOkHttpClient(
upstreamClient: OkHttpClient
): OkHttpClient {
return upstreamClient.newBuilder().build()
}
#Singleton
#Provides
fun provideRemoteDataSource(myMyAppService: MyMyAppApi) = RemoteDataSource(myMyAppService)
#Singleton
#Provides
fun provideDb(app: Application) = AppDatabase.getInstance(app)
//other code
This is Fragment Builder Module
#Suppress("unused")
#Module
abstract class FragmentBuildersModule {
#ContributesAndroidInjector
abstract fun homeFragment(): HomeFragment
#ContributesAndroidInjector
abstract fun fragHome(): FragHome
//other code
}
this is my Main Activity Module
#Suppress("unused")
#Module
abstract class MainActivityModule {
#ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): HomeActivity
#ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeSplashActivity(): SplashActivity
}
This is my ViewModel Module
#Suppress("unused")
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel
///other code
}
I tried to inject appwidgetprivider class using
AndroidInjection.inject(this)
as I did in Service. But this method only excepts Activity, Fragment, service, broadcast receiver and contentproviders. Any help please.
I am using dagger 2.23.2 and kotlin 1.3.41
Appwidget provicer can be injected the same way a broadcast receiver is injected.
By looking at your provided code you can do some thing like this.
Create an abstract function
#ContributesAndroidInjector
internal abstract fun contributeWidget(): YourWidgetClass
extend your Baseclass with HasBroadcastReceiverInjector and implement broadcastReceiverInjector
#Inject
lateinit var broadcastReceiverInjector: DispatchingAndroidInjector<BroadcastReceiver>
override fun broadcastReceiverInjector(): AndroidInjector<BroadcastReceiver> {
return broadcastReceiverInjector
}
and fillany inject in the widgetprovider class in onreceive
before super call
AndroidInjection.inject(this, context)
I have gone through all the answers with the above title but couldn't find the solution. Basically I want to do scoping. I want to inject ApiService only to HomeViewModel. It should not be available to LoginViewModel. I have my following setup and the error which I am getting:
Info: If I remove the provideLoginActivity() from ActivityModule everything works fine. Why behaving like that?
AppComponent:
#Singleton
#Component(
modules = [AndroidInjectionModule::class, ActivityModule::class, AppModule::class]
)
interface AppComponent : AndroidInjector<BaseApplication> {
#Component.Factory
interface Factory {
fun application(#BindsInstance baseApplication: BaseApplication): AppComponent
}
}
AppModule:
#Module
object AppModule {
#Singleton
#JvmStatic
#Provides
fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
ActivityModule:
#Module
abstract class ActivityModule {
#ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class])
internal abstract fun getHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [ViewModelBuilder::class])
internal abstract fun provideLoginActivity(): LoginActivity
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
NetworkModule:
#Module
object NetworkModule {
#JvmStatic
#Provides
fun getApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
ViewModelFactory:
class ViewModelFactory #Inject constructor(
private val creators: #JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown model class: $modelClass")
}
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
#Module
internal abstract class ViewModelBuilder {
#Binds
internal abstract fun bindViewModelFactory(
factory: ViewModelFactory
): ViewModelProvider.Factory
}
#Target(
AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
HomeViewModel:
class HomeViewModel #Inject constructor(private val apiService: ApiService) : ViewModel() {
val todoLiveData: LiveData<Todo> = liveData(Dispatchers.IO) {
val response: Todo = apiService.getTodo(1)
emit(response)
}
}
Error:
error: [Dagger/MissingBinding] com.sagar.daggertest.repository.network.ApiService cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.sagar.daggertest.BaseApplication> {
^
A binding with matching key exists in component: com.sagar.daggertest.di.HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent
com.sagar.daggertest.repository.network.ApiService is injected at
com.sagar.daggertest.HomeViewModel(apiService)
com.sagar.daggertest.HomeViewModel is injected at
com.sagar.daggertest.di.HomeActivityModule.bindHomeViewModel(homeViewModel)
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.sagar.daggertest.di.ViewModelFactory(creators)
com.sagar.daggertest.di.ViewModelFactory is injected at
com.sagar.daggertest.di.ViewModelBuilder.bindViewModelFactory$app_debug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.sagar.daggertest.LoginActivity.viewModelFactory
com.sagar.daggertest.LoginActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.sagar.daggertest.di.AppComponent → com.sagar.daggertest.di.HomeActivityModule_ProvideLoginActivity$app_debug.LoginActivitySubcomponent]
#ContributesAndroidInjector creates a subcomponent under the hood ( which is HomeActivityModule_GetHomeActivity$app_debug.HomeActivitySubcomponent in logs ).
In your ActivityModule, you're trying to provide HomeViewModel for map in ViewModelFactory, which is also injected at LoginActivity. But due to HomeViewModel needing ApiService and your ApiService is in NetworkModule which is scoped to subcomponent dagger generated - it fails.
Solution would be moving your multibinding to corresponding scope. By doing so, you're taking HomeViewModel out of map which is injected in LoginActivity, so it won't complain.
You can create a new module, let's say ViewModelModule and put your provider there:
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel
}
and pass it along with other modules to HomeActivity's contributor:
#ContributesAndroidInjector(modules = [ViewModelBuilder::class, NetworkModule::class, ViewModelModule::class])
internal abstract fun getHomeActivity(): HomeActivity
My scope:
#Scope
#Retention
annotation class ActivityScope
AppModule:
#Module(includes = [ActivityModule::class])
class AppModule {
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
#Provides
#Singleton
internal fun provideDataManager(appDataManager: AppDataManager): DataManager {
return appDataManager
}
}
UsecaseActivityModule :
#Module
class UsecaseActivityModule {
#Provides
#RegisterActivityScope
fun provideUsecase(appDataManager: AppDataManager): UseCase =
UseCase(appDataManager)
}
#Module
internal abstract class ActivityModule {
#ContributesAndroidInjector(
modules = [
UsecaseActivityModule::class
]
)
internal abstract fun contributeUsecaseActivity(): UsecaseActivity
}
Issue is I can not provide this:
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
got error
error: (unscoped) may not reference scoped bindings: #Subcomponent(
bla bla bla ...
AppComponent.java:6: error: com.example.di.AppComponent scoped with
#Singleton may not reference bindings with different
scopes:
#dagger.Component(modules = {dagger.android.AndroidInjectionModule.class,
com.example.di.ActivityModule.class, com.example.di.AppModule.class})
What am I doing wrong?
I did not specify scope in parent module with #RegisterActivityScope.
#Module
internal abstract class ActivityModule {
#RegisterActivityScope // <-- just need add this
#ContributesAndroidInjector(
modules = [
UsecaseActivityModule::class
]
)
internal abstract fun contributeUsecaseActivity(): UsecaseActivity
}
I've been checking recently Dagger 2.14.1 with the new Android injectors.
I'm using MVP and the Presenter is getting inject into the View correctly:
class CustomApplication : Application(), HasActivityInjector {
#Inject
lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
DaggerApplicationComponent
.builder()
.create(this)
.inject(this)
}
override fun activityInjector(): DispatchingAndroidInjector<Activity> {
return activityDispatchingAndroidInjector
}
}
--
#Singleton
#Suppress("UNUSED")
#Component(modules = arrayOf(AndroidInjectionModule::class, ApplicationModule::class, ActivityBuilder::class))
interface ApplicationComponent : AndroidInjector<CustomApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<CustomApplication>()
override fun inject(application: CustomApplication)
}
--
#Module
class ApplicationModule {
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
}
--
#Module
#Suppress("UNUSED")
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = arrayOf(ActivitiesModule::class))
internal abstract fun bindSplashActivity(): SplashActivity
}
--
#Singleton
class SplashPresenter #Inject constructor() {
fun test() {
Log.d("TAG", "this is a test")
}
}
Now, instead of having the logged message harcoded, I would like to get it from string.xml, so I tried this:
#Singleton
class SplashPresenter #Inject constructor(private val context: Context) {
fun test() {
Log.d("TAG", context.getString(R.strings.test))
}
}
But then I get this error:
Error:(7, 1) error: [dagger.android.AndroidInjector.inject(T)]
android.app.Application cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
Could anyone tell me please how to inject the app context (or the resources) into the presenter?
Thanks.
You're using CustomApplication with Dagger in your ApplicationComponent, so that's what it knows about. It doesn't try to resolve types on its own, so Application is some class Dagger never heard about.
You can either add another #Provides / #Binds to bind CustomApplication > Application > Context or just go the direct way and change your code to require a CustomApplication instead of Application:
#Provides
#Singleton
fun provideContext(application: CustomApplication): Context {
return application
}
// ... or alternatively ...
#Provides
#Singleton
fun provideApplication(application: CustomApplication): Application {
return application
}
#Provides
#Singleton
fun provideContext(application: Application): Context {
return application
}
Either way your application can then be used as a Context.