How to inject activity into another class - android

I need to #Inject constructor activity and fragmentManager into my Navigator.class. But I'm getting this error:
AppCompatActivity cannot be provided without an #Inject constructor or
from an #Provides-annotated method.
I've read all similar questions on Stackoverflow. I did everything they suggested, but didn't find the answer.
Navigator.class
#Singleton
class Navigator #Inject constructor(private val activity: AppCompatActivity,
private val fragmentManager: FragmentManager)
BaseActivity.class
abstract class BaseActivity: AppCompatActivity(), HasSupportFragmentInjector{
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
#Inject
lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector() = fragmentInjector
BaseModule.class
#Module
abstract class BaseActivityModule {
#Module
companion object {
#JvmStatic
#Provides
#Named(ACTIVITY_FRAGMENT_MANAGER)
#PerActivity
fun activityFragmentManager(activity: AppCompatActivity): FragmentManager =
activity.supportFragmentManager
}
#Binds
#PerActivity
abstract fun activity(appCompatActivity: AppCompatActivity): Activity
#Binds
#PerActivity
abstract fun activityContext(activity: Activity): Context
MainActivityModule.class
#Module(includes = [
BaseActivityModule::class
])
abstract class MainActivityModule {
#Binds
#PerActivity
abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity
AppModule.class
#Module(includes = [AndroidSupportInjectionModule::class])
abstract class AppModule {
#Binds
#Singleton
abstract fun application(app: App): Application
#PerActivity
#ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun mainActivityInjector(): MainActivity

First, please make sure to understand what the error means. You're trying to inject AppCompatActivity but Dagger complains that it cannot be provided. So far so good. Please always make sure to include the full error as shown in the link, so that we can more easily see what's going on. In your case this seems to be a scoping issue.
You provide a Navigator through constructor injection as a #Singleton...
#Singleton
class Navigator #Inject constructor(private val activity: AppCompatActivity,
private val fragmentManager: FragmentManager)
And you provide/bind AppCompatActivity from a module with #PerActivity scope.
#Binds
#PerActivity
abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity
So unless you have a really crazy setup, #Singleton will not be able to access #PerActivity. If it could, you'd have a memory leak at the very least, since you'd keep your Activity instance around longer than its lifetime (onCreate -> onDestroy)
To solve your problem you have to either move Navigator down in scope to also be #PerActivity and share the lifecycle of the Activities it references (so that it won't leak) or you have to remove the dependency on your Activity in your constructor. If you really do need it to be #Singleton and reference an Activity you might be able to have a setter for the current Activity, but again, watch out for leaks.

Related

Dagger 2.10+: Inject dependency with activity context in Activities and Fragments

I have a class called AlertManager which requires Activity instance to show Toast and AlertDialog.
class AlertManager #Inject constructor(private val activity: Activity) {
fun showToast(message: String) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
}
Now, I want AlertManager as dependency in two activities HomeActivity & ProductsActivity. Currently I have created modules for each Activity like:
#Module
class HomeActivityModule {
#Provides
#ActivityContext
fun provideAlertManager(activity: HomeActivity) = AlertManager(activity)
}
And
#Module
class ProductsActivityModule {
#Provides
#ActivityContext
fun provideAlertManager(activity: ProductsActivity) = AlertManager(activity)
}
And binding them with Dagger like
#Module
abstract class ActivityProvider {
#ContributesAndroidInjector(modules = [HomeActivityModule::class])
#ActivityContext
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [ProductsActivityModule::class])
#ActivityContext
abstract fun bindProductsActivity(): ProductsActivity
}
Now my questions are:
1) How can I avoid creating modules for each activities and have common ActivityModule which I can bind with whatever Activity I want?
2) Let's say I have a fragment called HomeFragment inside HomeActivity, then how can I inject the same AlertManager instance of HomeActivity inside the fragment?
I am stuck here since quite long and have tried to find a lot over internet but I am unable to find any blog or guide which can help me to achieve what I am looking for. If someone can point me in right direction, I'll be grateful.
1) How can I avoid creating modules for each activities and have common ActivityModule which I can bind with whatever Activity I want?
You can have some sort of AlertManagerModule where you add generic activity.
#Provides
fun provideAlertManager(activity: Activity) = AlertManager(activity)
You still will have to make individual activity modules. One change you can make is:
#Module
abstract class HomeActivityModule {
#Binds
abstract fun providesActivity(activity: HomeActivity) : Activity
}
And then you can add them to the ActivityProvider class:
#Module
abstract class ActivityProvider {
#ContributesAndroidInjector(modules = [HomeActivityModule::class, AlertManagerModule::class])
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [ProductsActivityModule::class, AlertManagerModule::class])
abstract fun bindProductsActivity(): ProductsActivity
}
2) Let's say I have a fragment called HomeFragment inside HomeActivity, then how can I inject the same AlertManager instance of HomeActivity inside the fragment?
Since you're using DaggerActivity and most likely using DaggerFragment, the fragment instantiated in the HomeFragment can directly get the AlertManager by simply using the #Inject annotation in the fragment provided you add in the HomeActivityModule:
#Module
abstract class HomeActivityModule {
#Binds
abstract fun providesActivity(activity: HomeActivity) : Activity
#FragmentScope
#ContributesAndroidInjector
abstract fun providesHomeFragment() : HomeFragment;
}

dagger2 and android: load module which injects viewmodel on a map

I've started using Dagger2, so there's still a lot to learn. I'm wondering if someone could point me on the right direction.
So, I've created a module for registering the view models used by my activities. It looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
ViewModelKey is a simple helper annotation class which looks like this:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey (val value: KClass<out ViewModel>) {
}
The ViewModelModule is loaded by my main app component (used for creating the app):
#Singleton
#Component(
modules=[
AndroidSupportInjectionModule::class,
AppModule::class,
DatabaseModule::class,
NewsServiceModule::class,
JobBindingModule::class,
ViewModelModule::class,
PreferencesModule::class,
ActivityBindingModule::class
]
)
interface AppComponent: AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder: AndroidInjector.Builder<MyApp>()
}
And here's the code for the ActivityBindingModule, responsible for setting up the subcomponents (in this case, activities used by my app):
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Internally, each activity is instantiating the view model with code that looks like this (called from within the onCreate method):
//view model code
_viewModel = ViewModelProviders.of(this, viewModelFactory)[ShowDetailsViewModel::class.java]
And, as you'd expect, viewModelFactory is injected as field:
#Inject lateinit var viewModelFactory: ViewModelProvider.Factory
Both view models have external dependencies which are set up on the other modules referenced by the top app component.
And, for the sake of completeness, here's the code for my view model factory:
#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
= viewModels[modelClass]?.get() as T
This code works, but it seems like it can be improved. After reading the docs, I'm under the impression that I could refactor my ViewModeModule so that it will simply instantiate my ViewModelFactory and move each of the view model declarations into separate module (so that each of them can be injected only in the "correct" activity).
In order to test this, I've started by moving the ShowDetailsViewModel into a new module which has only one entry:
#Module
internal abstract class DetailsModule {
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
After that, the ViewModelModule looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
}
And I've updated the ActivityBindingModule so that in looks like this:
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [DetailsModule::class])
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Notice that now I'm passing the DetailsModule (which instantiates the ShowDetailsViewModel) to the ContributeAndroidInjector annotation which is applied to the showDetailsActivity method because that view model is only used by that activity.
Now, I'm surely missing something because after doing this, I'm always getting the following exception:
java.lang.IllegalStateException: ViewModelProviders.of(th…ilsViewModel::class.java] must not be null
If I debug the app, I can see that moving the ShowDetailsViewModel into its own model does not register it on the map used by the factory (ie, the map has only one entry, which corresponds to the MainActivityViewModel that is registered in the ViewModelModule.
I thought that moving each view model the declaration into each a module used by a subcomponent should still allow it to be registered in a map injected by a module which is registered with the top component. Am I wrong? What is it that I'm missing to make this work?
Thanks.
The problem lies with ViewModelFactory being #Singleton and that it won't get any of the bindings you add in your subcomponents. Remove the scope from your factory or make it #ActivityScoped (the same scope as the ViewModel for the Activity)
The Activity (#ActivityScoped) has access to the factory (#Singleton), but the factory (#Singleton) does not have access to use or create the ViewModel from a lower scope (#ActivityScoped). So moving the factory to the same scope (#ActivityScoped) would give it access to create the viewmodel in question.

How does Dagger 2 dispose of injected fields when they are no longer needed?

I have a question about injecting multiple fragments into the activity's fields. Currently I have such a set up (all fragments extend DaggerFragment and the activity is DaggerAppCompatActivity):
#Inject
lateinit var fragmentOne: FragmentOne
#Inject
lateinit var fragmentTwo: FragmentTwo
#Inject
lateinit var fragmentThree: FragmentThree
override fun onCreate(...) {
...
startFirstFragment()
}
fun startFirstFragment() {
supportFragmentManager.beginTransaction()
.replace(containerId, fragmentOne).commit()
}
fun startSecondFragment() {
supportFragmentManager.beginTransaction()
.replace(containerId, fragmentTwo).commit()
}
And everything works fine, until I add LeakCanary, which says that, when I replace first fragment with the second, the instance being replaced leaks through lateinit var fragmentOne as it retains the reference to the first fragment. My question is: when does dagger empty the fields, does it do it correctly and who is to blame: dagger for causing leaks, LeakCanary for false positive leak detection or something else?
ApplicationComponent:
#ApplicationScoped
#Component(
modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
ApplicationModule::class,
RepositoriesModule::class,
NetworkModule::class]
)
interface ApplicationComponent : AndroidInjector<MyApp> {
override fun inject(instance: MyApp?)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
}
ActivityBindingModule:
#Module
abstract class ActivityBindingModule {
...
#ActivityScoped
#ContributesAndroidInjector(modules = [ActivityInQuestionModule::class])
internal abstract fun aiqActivity(): ActivityInQuestion
#ActivityScoped
#Binds
internal abstract fun fragmentSwitcher(activityInQuestion: ActivityInquestion): FragmentSwitcher
}
ActivityInQuestionModule:
#Module
abstract class ActivityInQuestionModule {
#FragmentScoped
#ContributesAndroidInjector
internal abstract fun fragmentOne(): FragmentOne
#FragmentScoped
#ContributesAndroidInjector
internal abstract fun fragmentTwo(): FragmentTwo
#FragmentScoped
#ContributesAndroidInjector
internal abstract fun fragmentThree(): FragmentThree
}
I'm pretty sure that dagger is not the reason of leak (if it's existed). What dagger do is just instantiating members of activity with appropriate instances. So you can try to run LeakCarnary with code modified like this: lateinit var fragmentOne = FragmentOne() ... and verify if memory leak exists or not. Maybe the problem is in your fragments code.

Dagger: A binding with matching key exists in component

I am using Dagger 2.16 and was following this article for my dagger implementation. Everything was working fine with this implementation until I had only one Activity(HomeActivity). As soon as I started implementing Dagger in SplashScreenActivity. I started getting this error. Here is some code from my project
AppComponent.kt
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ServiceBuilder::class,
BroadcastRecieverBuilder::class])
interface AppComponent : AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApp>()
}
AppModule.kt
#Module()
class AppModule {
#Provides
#Singleton
fun provideContext(application: MyApp): Context {
return application
}
#Provides
#Singleton
fun provideRestService(retrofit: Retrofit): RestService {
return retrofit.create(RestService::class.java)
}
...
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [HomeActivityModule::class])
#PerActivity
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [SplashScreenModule::class])
#PerActivity
abstract fun bindSplashActivity(): SplashScreenActivity
}
BaseActivity.kt
abstract class BaseActivity<V : BaseView, P : MvpBasePresenter<V>> :
MvpActivity<V, P>(), BaseView, HasSupportFragmentInjector {
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
#Inject
lateinit var mPresenter: P
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun createPresenter(): P = mPresenter
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return fragmentInjector
}
}
I have my own BaseActivity instead of DaggerActivity because I what to inherit from mosby's MvpActivity.
SplashScreenModule.kt
#Module
abstract class SplashScreenModule {
#Binds
#PerActivity
internal abstract fun splashPresenter(splashPresenter: SplashScreenPresenter): BasePresenter<*>
}
HomeActivityModule.kt
#Module
abstract class HomeActivityModule {
#Binds
#PerActivity
internal abstract fun homePresenter(homePresenter: HomeActivityPresenter): BasePresenter<*>
#ContributesAndroidInjector(modules = [DownloadFragmentModule::class])
#PerFragment
internal abstract fun downloadsFragment(): DownloadsFragment
}
Now when I build this, I get an error as follows
error: [Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> cannot be provided without an #Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.realtime.app.MyApp> {
^
A binding with matching key exists in component: com.realtime.dagger.ActivityBuilder_BindHomeActivity.HomeActivitySubcomponent
java.util.Map<java.lang.Class<? extends android.support.v4.app.Fragment>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>>> is injected at
dagger.android.DispatchingAndroidInjector.<init>(injectorFactories)
dagger.android.DispatchingAndroidInjector<android.support.v4.app.Fragment> is injected at
com.realtime.core.BaseActivity.fragmentInjector
com.realtime.splashScreen.SplashScreenActivity is injected at
dagger.android.AndroidInjector.inject(T)
component path: com.realtime.dagger.AppComponent → com.realtime.dagger.ActivityBuilder_BindSplashActivity.SplashScreenActivitySubcomponent
I have gone through other similar que like this but couldn't relate it to what I am facing. What am I missing?
Update: For now I am not inheriting BaseActivity in SplashScreenActivity so that I can avoid injecting fragmentInjector: DispatchingAndroidInjector<Fragment>. It is working for now as I don't have any fragment in SplashScreenActivity.
It works for HomeActivity because it binds a fragment:
#ContributesAndroidInjector
fun downloadsFragment(): DownloadsFragment
SplashScreenActivity does not.
AndroidInjection uses DispatchingAndroidInjector to handle runtime injections, which basically contains a Map of classes to their component builders. This map needs to be injected like everything else. In the case of HomeActivity the fragment declaration in the module generates a binding for the map, which can then be injected.
Since there is no Fragment on the splash activity Dagger does not know about any bindings, let alone any map. Which is why it complains that it cannot be provided.
You can read more here about multibindings.
To prevent this from happening, you should register AndroidInjectionModule on your AppComponent, which just contains the declarations for the empty maps.
While it contains the declaration for android.app.Fragment it does not for android.support.v4.app.Fragment, which is where the error comes from.
So to fix this specific error you should add AndroidSupportInjectionModule to your component, which also includes the support bindings, providing an empty map when there are no fragments in an activity.
#Component(modules = [AndroidSupportInjectionModule::class, /* ... */])
interface AppComponent { /* ... */ }

Dagger 2 injecting view model of activity into fragment

(using kotlin) I have an app that uses a settings activity with 2 fragments. I'd like both to get the same instance of the SettingsViewModel as the activity. I assume there's a scoping issue that I'm missing.
First, I have the standard ViewModelModule:
#Module
abstract class ViewModelModule {
#Binds #IntoMap
#ViewModelKey(SettingsViewModel::class)
abstract fun bindSettingsViewModel(viewModel: SettingsViewModel): ViewModel
#Binds
abstract fun bindViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
I bind my activities in:
#Module
abstract class AndroidBindingModule {
#PerActivity
#ContributesAndroidInjector(modules = [SettingsActivityModule::class])
abstract fun contributeSettingsActivity(): SettingsActivity
}
With all other things in place this works well and SettingsActivity does get an instance of the SettingsViewModel. SettingsActivityModule adds the following:
#PerFragment
#ContributesAndroidInjector
abstract fun contributeMainSettingsFragment(): MainSettingsFragment
#PerFragment
#ContributesAndroidInjector
abstract fun contributeDebugSettingsFragment(): DebugSettingsFragment
Both fragments appear to have the injectors called on them (I've checked through the debugger and AndroidSupportInjection.inject(fragment) is called). The fragments include:
#Inject lateinit var mainViewModel: SettingsViewModel
But in my fragment's onCreate() I'm seeing that mainViewModel is still null. Is there anything special I need to do here to avoid calling ViewModelProviders.of(activity)[SettingsViewModel::class.java] and instead injecting the view model?
UPDATE:
After a bit more reading I found that the correct way of using the view model injection in fragments is to inject the factory and get the view model in onActivityCreated:
#Inject lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var mainViewModel: SettingsViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainViewModel = ViewModelProviders.of(activity, viewModelFactory)[SettingsViewModel::class.java]
}
This makes sense since I have MyViewModelFactory bound as the ViewModelProvider.Factory and it's annotated with #Singleton. When I try to compile the above I get the following error:
Error:(6, 1) error: [dagger.android.AndroidInjector.inject(T)] java.util.Map<kotlin.reflect.KClass<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
It appears that Dagger can't find the mapping created by the ViewModelModule. I'm still at a loss at how that can be. Maybe my tree is incorrect? Why would activities in AndroidBindingModule be able to get the ViewModel but not fragments?
AppComponent
- AndroidInjectionModule
- AndroidBindingModule
- AppModule
- SdkModule
- ViewModelModule
- GotItCardModule
- ViewHolderSubcomponent (provides a mapping of layout ID -> ViewHolder for a factory)
UPDATE
I've done a little more digging into this... From the full error:
e: /home/user/workspace/Example/sdktest/build/tmp/kapt3/stubs/debug/com/example/sdktest/di/AppComponent.java:6: error: [dagger.android.AndroidInjector.inject(T)] java.util.Map<kotlin.reflect.KClass<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an #Provides-annotated method.
e:
e: public abstract interface AppComponent {
e: ^
e: java.util.Map<kotlin.reflect.KClass<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> is injected at
e: com.example.sdktest.di.viewmodel.ExampleViewModelFactory.<init>(creators)
e: com.example.sdktest.di.viewmodel.ExampleViewModelFactory is injected at
e: com.example.sdktest.di.viewmodel.ViewModelModule.bindViewModelFactory(factory)
e: android.arch.lifecycle.ViewModelProvider.Factory is injected at
e: com.example.sdktest.ui.settings.fragment.MainSettingsFragment.viewModelFactory
e: com.example.sdktest.ui.settings.fragment.MainSettingsFragment is injected at
e: dagger.android.AndroidInjector.inject(arg0)
I think the issue is that somehow Dagger is trying to inject my fragment with dagger.android.AndroidInjecton instead of dagger.android.AndroidSupportInjection. Still not sure how to fix.
OK, so I found the answer and it wasn't anywhere near what I thought. My translation of the GithubViewModelFactory into Kotlin included the following constructor:
#Singleton
class MetaverseViewModelFactory #Inject constructor(
creators: Map<KClass<out ViewModel>, Provider<ViewModel>>
): ViewModelProvider.Factory {
private val creators: Map<Class<out ViewModel>, Provider<ViewModel>> =
creators.mapKeys { it.key.java }
//...
}
This was due to the ViewModelKey in Kotlin being able to use KClass only instead of Class. It turns out that kapt takes care of this and the correct factory should look like:
#Singleton
class MetaverseViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
//...
}
Note the additional #JvmSupressWildcards to also avoid turning Provider<ViewModel> into Provider<? extends ViewModel>

Categories

Resources