Inject Activity into another dependent class - android

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

Hilt inject to viewmodel using the same way as to activity

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 ?

Dagger2 Missing Binding Cannot Provides ViewModel Key

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
}
}

ViewModel cannot be provided without an #Inject constructor or an #Provides-annotated

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

Dagger MissingBinding error when injecting ViewModels

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

Inject ViewModel with Dagger2

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>)

Categories

Resources