I'v been trying for some time now to get my dagger2 code setup correctly in my project, but I can't seem to figure out how do it correctly. My app roughly looks like this:
class MyApplication : DaggerApplication() {
...
}
class MainActivity : DaggerAppCompatActivity() {
...
}
abstract class BaseFragment() : DaggerFragment() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
...
fun <T : ViewModel> getViewModel(c: Class<T>): T = getViewModel(c, requireActivity())
fun <T : ViewModel> getViewModel(c: Class<T>, owner: ViewModelStoreOwner): T{
return ViewModelProvider(owner, viewModelFactory)[c]
}
}
class AConcreteFragment : BaseFragment() {
private lateinit var viewModel: AViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = getViewModel(AViewModel::class.java)
}
class AViewModel #Inject constructor(private val someApiManager: SomeApiManager ) : ViewModel() {
...
}
class SomeApiManager #Inject constructor(private val activity: MainActivity) {
...
fun authenticate(){
3rdPartyLibrary.openLoginActivity(activity,...)
}
}
Before I introduced SomeApiManager which depends on MainActivity everything worked fine. Then I had dagger2 setup to something like this:
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ApplicationModule::class,
FragmentBuilder::class,
ViewModelModule::class,
...
]
)
interface ApplicationComponent: AndroidInjector<MyApplication> {
...
}
#Module
public abstract class FragmentBuilder {
#ContributesAndroidInjector()
abstract fun bindAConcreteFragment(): AConcreteFragment?
...
}
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
...
}
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
internal abstract fun aViewModel(viewModel: AViewModel): ViewModel
...
}
But how should change my dagger2 code to be able to provide MainActivity to SomeApiManager?
As #DavidMedenjak mentioned I did not scope things correctly. My modified dagger2 code now looks like this:
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ApplicationModule::class,
ActivityBuilder::class,
...
]
)
interface ApplicationComponent: AndroidInjector<MyApplication> {
...
}
#Module()
public abstract class ActivityBuilder {
#ActivityScope
#ContributesAndroidInjector(modules = [
FragmentBuilder::class,
ViewModelModule::class
])
abstract fun bindMainActivity(): MainActivity?
}
#Module
public abstract class FragmentBuilder {
#FragmentScope
#ContributesAndroidInjector()
abstract fun bindAConcreteFragment(): AConcreteFragment?
...
}
#ActivityScope
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
...
}
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
internal abstract fun aViewModel(viewModel: AViewModel): ViewModel
...
}
Not sure if it it's entirely correct, still not fully grasping everything that's going on. But it does work.
Related
I'm wondering, why my injecting works only in activity:
it works:
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject lateinit var fakeRepo: FakeRepo
}
#Module
#InstallIn(ActivityComponent::class)
abstract class MainModule {
#Binds
abstract fun bindFakeRepo(fakeRepoImpl: FakeRepoImpl): FakeRepo
}
BUT when I'm trying inject this repo to viewModel this way it gives errors:
#Module
#InstallIn(MainActivityViewModel::class)
abstract class MainModule {
#Binds
abstract fun bindFakeRepo(fakeRepoImpl: FakeRepoImpl): FakeRepo
}
interface FakeRepo {
fun getData(): List<Int>
}
class FakeRepoImpl #Inject constructor(): FakeRepo {
override fun getData(): List<Int> {
return listOf(3,5,6)
}
}
#HiltViewModel
class MainActivityViewModel #Inject constructor(val fakeRepo: FakeRepo) : ViewModel() {
}
Why it doesn't work ? I get
#InstallIn, can only be used with #DefineComponent-annotated classes
Probably I can inject it using provide as singleton but this is not the solution.
Could someone explain why I can inject to activity but to viewmodel not using #Binds ?
I’m trying to create dependency injection of my ViewModel using Dagger2 with multi binds but I’m receiving this error and I can’t make it work, I tried several answers (below) but none of them helped me.
This is the error I receive :
SaveMyHeroApplicationComponent.java:8: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
And this is my code
class SaveMyHeroApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerSaveMyHeroApplicationComponent.factory().create(this)
}
}
#Singleton
#Component(modules = [AndroidInjectionModule::class, MainActivityModule::class])
interface SaveMyHeroApplicationComponent : AndroidInjector<SaveMyHeroApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): SaveMyHeroApplicationComponent
}
}
#Module(includes = [NetworkModule::class, HomeModule::class])
class MainActivityModule {
#Provides
fun provideViewModelFactoryProviders(
providers: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory = SaveMyHeroViewModelFactory(providers)
}
class SaveMyHeroViewModelFactory(
private val providers: Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T =
requireNotNull(getProvider(modelClass).get()) {
"Provider for $modelClass returned null"
}
private fun <T : ViewModel> getProvider(modelClass: Class<T>): Provider<T> =
try {
requireNotNull(providers[modelClass] as Provider<T>) {
"No ViewModel provider is bound for class $modelClass"
}
} catch (error: ClassCastException) {
error("Wrong provider type registered for ViewModel type $error")
}
}
#Module(includes = [HomeModule.ProvideViewModel::class])
abstract class HomeModule {
#ContributesAndroidInjector(modules = [InjectViewModel::class])
abstract fun bind(): HomeFragment
#Module
class ProvideViewModel {
#Provides
#IntoMap
#ViewModelKey(HomeViewModel::class)
fun provideHomeViewModel() = HomeViewModel()
}
#Module
class InjectViewModel {
#Provides
fun provideHomeViewModel(
factory: ViewModelProvider.Factory,
target: HomeFragment
) = ViewModelProvider(target, factory).get(HomeViewModel::class.java)
}
}
#MustBeDocumented
#Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
Also, these are my app dependencies version:
kotlin_version = ‘1.3.72'
dagger_version = ‘2.27’
gradle:3.6.3
I know there are several questions with this problem but I tried several of them and none of them worked for me.
This are the solutions links that I tried reading and check:
https://github.com/android/architecture-components-samples/tree/master/GithubBrowserSample
https://github.com/google/dagger/issues/1478
Dagger/MissingBinding java.util.Map<java.lang.Class<? extends ViewModel>,Provider<ViewModel>> cannot be provided without an #Provides-annotated method
https://github.com/google/dagger/issues/1478
Error [Dagger/MissingBinding] androidx.lifecycle.ViewModelProvider.Factory cannot be provided without an #Provides-annotated method
https://medium.com/chili-labs/android-viewmodel-injection-with-dagger-f0061d3402ff
https://github.com/ChiliLabs/viewmodel-dagger-example
Try to use Architecture Blueprints sample (dagger-android branch) as an example.
Dagger Android is a mess itself and it's important to follow some template not to turn wrong way. May be your approach could be fixed as well, but I propose you to try change your schema:
You custom View Model factory should have #Inject in constructor:
class SaveMyHeroViewModelFactory #Inject constructor(
private val creators: #JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
.......
You should add auxiliary module ViewModelBuilderModule, that provides ViewModelProvider.Factory (that you inject in all your activities and fragments) with your custom ViewModel.Factory:
#Module
abstract class ViewModelBuilderModule {
#Binds
abstract fun bindViewModelFactory(factory: SaveMyHeroViewModelFactory): ViewModelProvider.Factory
}
For all your pairs - Activitiy/ViewModel and Fragment/ViewModel you should add Module like this (but you can make single module for all of them, it's up to you):
#Module
abstract class HomeModule {
#ContributesAndroidInjector(modules = [ViewModelBuilderModule::class])
internal abstract fun bind(): HomeFragment
#Binds
#IntoMap
#ViewModelKey(HomeViewModel::class)
internal abstract fun provideHomeViewModel(viewModel: HomeViewModel): ViewModel
}
In your Dagger component you should use all modules from step 3:
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class, HomeModule::class, ...])
interface SaveMyHeroApplicationComponent : AndroidInjector<SaveMyHeroApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): SaveMyHeroApplicationComponent
}
}
Question EDITED
I am injecting ViewModelProvider.Factory to BaseActivity like below
open class BaseActivity : DaggerAppCompatActivity() {
#Inject
lateinit var factories: ViewModelProvider.Factory
inline fun <reified T : ViewModel> getViewModel(): T {
return ViewModelProvider(this, factories).get(T::class.java)
}
}
viewModel only works when we inject then like below.
class MainViewModel #Inject constructor( private val alertStore: AlertStore)
: BaseViewModel(){
fun showDialog(){
viewModelScope.launch {
delay(4000)
alertStore.showToast("Alert after 4 seconds.")
}
}
}
Why this #Inject constructor is necessary in my current implementation
class MainActivity : BaseActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = getViewModel()
viewModel.showDialog()
}
}
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
AppComponent.kt
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ViewModelInjector::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
fun provideViewModelFactories(viewModels: Map<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>):
ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val factory = viewModels[modelClass]?.get() ?: error(
"No factory provided against ${modelClass.name}"
)
#Suppress("UNCHECKED_CAST")
return factory as T
}
}
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
//#Scope("")
#ContributesAndroidInjector ///(modules = {MainModelFactory.class})
public abstract MainActivity bindMainActivity();
}
ViewModelInjector.kt
#Module
public abstract class ViewModelInjector {
#Binds
#IntoMap
#ViewModelKey(MainViewModel.class)
public abstract ViewModel providesMainViewModel(MainViewModel model);
}
ViewModelKey.kt
#MapKey
#Retention(AnnotationRetention.SOURCE)
annotation class ViewModelKey(
val value: KClass<out ViewModel>
)
Why do I have to append #Inject constructor to each ViewModel and kindly explain a little why we need #Binds #IntoMap and with ViewModel
When you use dagger android you should make your activities and fragments as extensions of DaggerActivity (and DaggerFragment for fragments).
class MainActivity : DaggerActivity() {
#Inject
lateinit var viewModel: MainViewModel
}
Next you should prepare infrastructure for injection:
Create injectors for each your activity:
// All your injectors can be defined in this module
#Module(includes = [AndroidInjectionModule::class])
interface AppInjectorModule {
#ContributesAndroidInjector(modules = [MainActivityVmModule::class, /*other dependecies*/])
fun getMainActivityInjector(): MainActivity
}
Create modules to provide view models (can be multiple for one activity) and factory
#Module(includes = [VmFactoryModule::class])
abstract class MainActivityVmModule {
// bind implementation of ViewModel into map for ViewModelFactory
#Binds
#IntoMap
#ClassKey(MainViewModelImpl::class)
abstract fun bindMainVm(impl: MainViewModelImpl): ViewModel
#Module
companion object {
#Provides
#JvmStatic
fun getMainVm(activity: MainActivity, factory: ViewModelProvider.Factory): MainViewModel {
// create MainViewModelImpl in scope of MainActivity and inject dependecies by ViewModelFactory
return ViewModelProviders.of(activity, factory)[MainViewModelImpl::class.java]
}
}
}
Factory can be provided by different module to avoid duplication
#Module
interface VmFactoryModule {
#Binds
// bind your implementation of factory
fun bindVmFactory(impl: ViewModelFactory): ViewModelProvider.Factory
}
Add activities injectors to AppComponent graph
#Component(
modules = [
AppInjectorModule::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App>
Additional info: Dagger & Android
I'm new to dagger and want to use it for injecting ViewModels (along with other objects such as repositories). When I try to compile the app this error is shown:
e: C:\Project\...\app\build\tmp\kapt3\stubs\debug\com\myapp\di\AppComponent.java:16: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent {
^
java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
com.myapp.di.ViewModelFactory(viewModels)
com.myapp.di.ViewModelFactory is injected at
com.myapp.di.ViewModelModule.bindViewModelFactory(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.myapp.ui.activity.MainActivity.viewModelFactory
com.myapp.ui.activity.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.myapp.di.AppComponent ? com.myapp.di.ActivityModule_ContributeMainActivity.MainActivitySubcomponent]
The following other entry points also depend on it:
dagger.android.AndroidInjector.inject(T) [com.myapp.di.AppComponent ? com.myapp.di.ActivityModule_ContributeItemsFrament.ItemsFragmentSubcomponent]
Maybe there is one silly mistake that prevents compilation.
Dagger component:
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class, AppModule::class, DatabaseModule::class, ViewModelModule::class, ActivityModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: MyCustomApplication): Builder
fun build(): AppComponent
}
fun inject(app: MyCustomApplication)
}
AppModule:
#Module
class AppModule {
#Inject
lateinit var app: Application
#Provides
#Singleton
fun provideAppContext(): Context = app.applicationContext
}
ActivityModule:
#Module
abstract class ActivityModule {
#ContributesAndroidInjector
abstract fun bindMainActivity(): MainActivity
#ContributesAndroidInjector
abstract fun contributeMainFrament(): MainFragment
}
ViewModelModule:
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainViewModel::class)
abstract fun mainViewModel(mainViewModel: MainViewModel): ViewModel
}
ViewModelFactory:
#Singleton
class ViewModelFactory
#Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>)
: ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModels[modelClass]
?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
The application class:
class MyCustomApplication : Application(), HasActivityInjector, HasSupportFragmentInjector {
#Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
DaggerMainComponent
.builder()
.application(this)
.build()
.inject(this)
}
override fun activityInjector() = activityInjector
}
MainViewModel (note that it extends from AndroidViewModel and requires an application context):
class MainViewModel #Inject constructor(app: Application, private val repository: MainRepository)
: AndroidViewModel(app) {
MainActivity:
class MainActivity : BaseActivity() {
#Inject
internal lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: MainViewModel
override fun onCreate(savedState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedState)
viewModel = ViewModelProviders.of(this,viewModelFactory).get(MainViewModel::class.java)
I faced same issue combining Android X and Kotlin version 1.3.0, Dagger won't be able to create MultiBindings. To solve the problem I just followed next steps:
a) Update Kotlin version to 1.3.31 on build.gradle file:
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin: 1.3.31"
}
b) Clean and rebuild the project, to do so, execute the following command in a terminal:
$ gradle clean build
or if using gradle wrapper:
$ ./gradlew clean build
I have tried updating the kotlin-gradle-plugin to 1.3.31 but it didn't work for me.
After spending some time on the internet, I have found the following solution which is working for me.
Try adding #JvmSuppressWildcards before Provider<ViewModel> like following:
#Singleton
#Provides
fun provideViewModelFactory(creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>): ViewModelFactory {
return ViewModelFactory(creators)
}
And in ViewModelFactory as well.
class ViewModelFactory(private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) :
ViewModelProvider.Factory
I am building an Android application with Dagger2 and the new Architecture Components. I have tried to make ViewModels injectable following this tutorial: https://blog.kotlin-academy.com/understanding-dagger-2-multibindings-viewmodel-8418eb372848
When I run my code, I get the following error:
[AndroidInjector.inject(T) Map<Class<? extends ViewModel>, Provider<ViewModel>> cannot be provided without an #Provides-annotated method.
My code looks like this:
#Singleton
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass]
?: creators.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
?: throw IllegalArgumentException("Unknown model class $modelClass")
return creator.get() as T
}
}
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
#MapKey
#Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
annotation class ViewModelKey(val value: KClass<out ViewModel>)
I the module is also included in the AppComponent:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ViewModelFactoryModule::class // ...
])
interface AppComponent : AndroidInjector<BaseApplication>
class BaseApplication : DaggerApplication(), HasActivityInjector {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().appModule(AppModule(this)).build()
}
The ViewModel I try to inject is declared in a Module like this:
#Module
abstract class SettingsModule {
#Binds
#IntoMap
#ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(model: SettingsViewModel): ViewModel
}
Does anybody have an idea what the problem could be?
It's hard to point exact issue with Dagger. Try moving the
#Binds
#IntoMap
#ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(model: SettingsViewModel): ViewModel
from SettingsModule to ViewModelFactoryModule as that's what I am doing in my code-base and it's working.
#Module
abstract class ViewModelFactoryModule {
#Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory):
ViewModelProvider.Factory
}
OR
Try changing your ViewModelKey to this:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)