Originally posted as an issue on the Dagger2 repo.
Summary: I have an activity with one fragment that has setRetainInstance(true). Despite the fragment being retained, every time I call AndroidSupportInjection.inject(this) on it, it injects new instances of its dependencies. It looks like the fragment subcomponent is being recreated (I think?) each time the activity subcomponent is recreated (on rotation).
Is this expected, or is my graph misconfigured?
I have an app component like so:
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivitiesModule::class,
AndroidViewInjectionModule::class,
NetModule::class
])
interface MainApplicationComponent {
fun inject(app: MainApplication)
#Component.Builder
interface Builder {
fun build(): MainApplicationComponent
#BindsInstance fun app(app: Context): Builder
// ... other things ...
}
}
ActivitiesModule looks like:
#Module
abstract class ActivitiesModule {
// ... other things ...
#ActivityScoped
#ContributesAndroidInjector(modules = [
UpgradeActivityModule::class,
UpgradeFragmentModule::class
]) abstract fun upgradeActivity(): UpgradeActivity
}
UpgradeFragmentModule:
#Module
abstract class UpgradeFragmentModule {
#FragmentScoped
#ContributesAndroidInjector(modules = [
UpgradeActivity.UpgradeFragmentModule::class,
ViewInjectorModule::class
]) abstract fun upgradeFragment(): UpgradeFragment
}
And UpgradeActivity.UpgradeFragmentModule (this is all very much WIP, sorry for weird names):
#Module
abstract class UpgradeFragmentModule {
#Binds #FragmentScoped abstract fun bindUpgradeModel(model: UpgradeModel): UpgradeMvp.Model
#Binds #FragmentScoped abstract fun bindUpgradePresenter(presenter: UpgradePresenter): UpgradeMvp.Presenter
// ... other things ...
#Module
companion object {
#Provides #JvmStatic fun provideResources(activityProvider: Provider<UpgradeActivity>): Resources {
return activityProvider.get().resources
}
// ... other things ...
}
}
I experimented further and tried to make my #FragmentScoped elements direct descendants of my #Singleton app component, but it has the same issue. In fact, if I just inject my fragment twice in a row, I get new instances each time. Clearly I'm doing something wrong....
I strongly encourage you to simply have a look at the Dagger Android source code, since it's only a few classes that do all the work.
[...] every time I call AndroidSupportInjection.inject(this) on it, it injects new instances of its dependencies. It looks like the fragment subcomponent is being recreated [...]
That's exactly what's going on.
To give an inaccurate and simplified summary, you register your Subcomponent.Builders in a Map, and when you call AndroidInjection.inject() it will look up and create the right Builder and Component, with which it will then inject the object.
You should never inject objects multiple times as this will accomplish nothing in the best case, or lead to errors/bugs otherwise. Scopes are per component, so if you recreate the component, you recreate every object within its scope along with it. And calling AndroidInjection.inject() will always create a new component.
You don't go into detail about what you inject when and where, but if you keep the same fragment object around, you should not inject it again.
[...] and tried to make my #FragmentScoped elements direct descendants of my #Singleton app component, but it has the same issue.
That's what you should do. If you use setRetainInstance(true), then the fragment should most likely not be a Subcomponent of your UpgradeActivity, or it will leak the reference when the Activity gets recreated.
if I just inject my fragment twice in a row, I get new instances each time.
If you call AndroidInjection.inject() then it will create a new component with every call, so I assume that's what you did and observed. If you inject an object twice with the same component, then any scoped objects will be the same. Unscoped objects will always be created for every use. But in any case, you should never inject an object more than once.
Related
I inject MyObject in FirstFragment by Hilt, This fragment replaces with another fragment, and as well as you know, when one fragment is replaced with another, onDestroyView() called and when you press back, the view will be created again.
I need to create a new instance of MyObject whenever the fragment's view is created. in Koin I could get directly an instance inside of my code with get<MyObject>() but in Hilt, I couldn't find anything and I have to inject it as a constructor or property.
I also tried to use ViewWithFragmentComponent to change my scope, but I get an error when I use this instead of ApplicationComponent
How can I have a new instance of MyObject whenever my view created in my fragment?
#AndroidEntryPoint
class FirstFragment : BaseFragment(){
#Inject lateinit var myObject: MyObject
}
#InstallIn(ViewWithFragmentComponent::class)
#Module
object MyModule{
#Provides
internal fun provideMyObject(): MyObject {
...
}
}
As far as I know, whenever you inject something into a fragment and this fragment gets destroyed, the injected object gets destroyed as well. So in your example, when onDestroyView() is called myObject will be garbage collected. Clicking the back button then will trigger onCreateView() and dagger-hilt will automatically provide a new instance of myObject.
If you always want the same object, you have to annotate it with #Singleton. Optional, you can also use #FragmentScoped, where the documentation says:
Scope annotation for bindings that should exist for the life of a fragment.
But to my mind, this is not needed as I personally found at that an object provided by dagger-hilt gets recreated when the fragments gets recreated. Using #FragmentScope made my life harder as it introduced some other bugs.
You can look here to learn more about scoping and the functionality of dagger-hilt
To get instance from hilt directly you should use EntryPoint annotation:
example code can be found here:
https://developer.android.com/codelabs/android-hilt#10
Steps are as follows:
Declare in your non-Hilt aware class an interface implementing a method returning interface of instance you want to retrieve from Hilt.
Annotate this interface with
#InstallIn(SingletonComponent::class)
#EntryPoint
assuming your instance is a singleton, so it would look as follows:
class MyAnyKindOfClass {
#InstallIn(SingletonComponent::class)
#EntryPoint
interface MyObjectEntryPoint{
fun getMyObject(): MyObject;
}
fun getMyObject(): MyObject {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
appContext, // You need to provid ApplicationContext
MyObjectEntryPoint::class.java)
return hiltEntryPoint.getMyObject()
}
}
I have been using Dagger 2 in my project. I understand that the lifetime of scoped object is the same as the lifetime of component (with the same scope). What about the lifetime of component then?
For example, I have a component:
#MyApp
#Component(modules = {
ApplicationModule.class})
public interface ApplicationComponent {
// Injection methods
void inject(MyApplication mainApplication);
}
I build component by:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
buildApplicationComponent();
mApplicationComponent.inject(this);
}
private void buildApplicationComponent() {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
Currently, the code to build component is in Application class. But
is it so that if I build component in Fragment the ApplicationComponent would have the same lifetime as the fragment & if I execute it in Application class the component would have the same lifetime as the whole application? Or how the lifetime of component is defined?
It is plain Java. You have object no matter where. The object provides you dependencies. If you use the same instance you will have the same dependencies or basically the same "Scope/lifetime". If you create somewhere new object it means new other object\dependencies it can provide so "another Scope/lifetime". If you share the object between different objects (You create it in the Application class, but reuse it in another fragment) you are in the "same Scope/lifetime".
But in general I see only "old way" of using Dagger 2 here. This is how your classes should end up everywhere. No need to find reference to any Components and etc or try to instantiate them on your own or clear them on your own in Fragments or Actvities. You have some code for Dagger 2 in one place and then some Annotations in you classes "which do the real work of your project". No need for complicated "connection" between the two parts and I see here a lot of cases like this which are part of tutorials that date back to 2016-2017...
class Repostory #Inject constructor(
private val dependency1: Dependency1
) {}
class Activity or Fragment {
#Inject lateinit var dependency2: Dependency2
}
This is a great example for right usage of Dagger2.
https://github.com/google/iosched
Here is an article about the app:
https://medium.com/#JoseAlcerreca
Jose Alcerreca is one of the Google Lead Devs responsible for creating the guidelines to write Android apps.
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
}
Just try to compile something like this. There is the exaxt same class in the app I gave you. Check the generated code and also the docs. If you see ContributesAndroidInjector there is explained:
Generates an {#link AndroidInjector} for the return type of this method. The injector is
implemented with a {#link dagger.Subcomponent} and will be a child of the {#link dagger.Module}'s component.
If you check the AndroidInjector docs you will see that it is the one that is injectin the Activity. And there it will be the implementation:
#Subcomponent(modules = {MainActivityModule.class})
#ActivityScoped
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
So in general these annotations do the magic for you when a new activity starts. They create the Subcomponent, they use it and when the Activity Lifecycle ends the Subcomponent is also dead. Dagger clears the reference. Dagger knows how Activities work or Fragments and etc. There was the old way when you craete the component in the Activity, use it, call inject on your own. Or put some component in the AppClass and then clear it on your onw. But now there is a bigger amount of annotations.
I want to use Dagger to compose a module for AFragment with child fragments (X...Z)FragModule.
Starting out from the ActivityBindingModule, I define the an example activity's dependent modules
/* ActivityBindingModule.java */
#Module
public abstract class ActivityBindingModule{
#NonNull
#ActivityScoped
#ContributesAndroidInjector(modules = {
AFragModule.class,
BFragModule.class
// ... fragment modules
})
abstract MainActivity mainActivity();
}
In AFragModule, I define its child fragment dependent modules.
/* AFragModule.java */
#Module
public abstract class AFragModule{
#NonNull
#FragmentScoped
#ContributesAndroidInjector(modules = {
XFragModule.class,
YFragModule.class
// ... child fragment modules
})
abstract AFragment providesFragment();
}
In (X...Z)FragModule, I defined its dependent objects' provider methods.
/* XFragModule.java */
#Module
public abstract class XFragModule{
#FragmentScoped
#ContributesAndroidInjector
abstract XFragment providesFragment();
#ActivityScoped
#Binds
abstract XContract.Presenter providesPresenter(XPresenter presenter);
}
I want the (X...Z)FragModule modules to be scoped within the AFragModule. And (X...Z)FragModules' presenters alive within the same scope for inter-presenter communication.
The XPresenter implementation itself uses constructor injector, with #Singleton parameters (eg. datasources)
I get the following error with the above scopes:
Cause: binding is not resolved for XContract.Presenter: ProvisionBinding{contributionType=UNIQUE, key=XContract.Presenter, bindingElement=Optional[providesPresenter(XPresenter)], contributingModule=Optional[XFragModule], kind=DELEGATE, nullableType=Optional.empty, wrappedMapKeyAnnotation=Optional.empty, provisionDependencies=[DependencyRequest{kind=INSTANCE, key=XPresenter, requestElement=Optional[presenter], isNullable=false}], injectionSites=[], unresolved=Optional.empty, scope=Optional[#ActivityScoped]}
EDIT:
I want to check my understanding of scope as well:
I "think" I understand the following
#Singleton > #ActivityScope > #FragmentScope
Scopes can't depend on the same or smaller scope
ex. #ActivityScope can't depend on #ActivityScope or #FragmentScope
Scopes can depend on any bigger scope.
ex. #FragmentScope can depend on #Singleton, #ActivityScope marked methods.
In your specific case, it looks like Dagger can't find the binding for XPresenter; you have a statement that #Binds XContract.Presenter to XPresenter, but based on your casual mention of "The XPresenter implementation itself" it looks like you might be missing a statement like:
#Binds
abstract XPresenter providesXPresenter(XPresenterImpl presenterImpl);
Scopes can depend on the same scope: Items in #ActivityScope can depend on #ActivityScope but not #FragmentScope. If you try to depend on a #FragmentScope object from #ActivityScope (a "scope-widening injection"), Dagger will prevent it and describe the components where you can find that injection.
However, you also will likely run into the trouble that a #FragmentScope component cannot contain other #FragmentScope components, which is a problem given that #ContributesAndroidInjector necessarily creates a new subcomponent that takes the scope and modules listed on the method. You'll need to adjust your choice of scope annotations, such as creating and using #ParentFragmentScope and #ChildFragmentScope. This is also important because your #ChildFragmentScope Fragment XFragment can inject objects that share the lifecycle of XFragment's subcomponent instance, AFragment's subcomponent instance, MainActivity's subcomponent instance, or your root #Singleton component.
Of course, you'll probably want to name them according to your use case, like #FullScreenFragmentScope or #TabFragmentScope or #OptionalFlowFragmentScope; you can also choose the outer scope to keep #FragmentScope and the inner scope to be #SubFragmentScope or so forth, which might be particularly useful if you have reusable modules that already use #FragmentScope. The point is precisely that a reusable module that uses #FragmentScope is not going to be clear about whether it is tracking AFragment's lifecycle or XFragment's lifecycle, so you're going to need to be clearer about that.
one object if injected into 2 subcomponents under same custom scope, every time new instance is created of that object. I want same instance to be passed to all subcomponents
this is the module
#CustomScope
#Module
public class EventBusModule {
PublishSubject<Boolean> bus = PublishSubject.create();
#CustomScope
#Provides
public PublishSubject<Boolean> provideRxBus() {
return bus;
}
}
these are my subcomponents
#Module
public abstract class ActivityBindingModule {
#CustomScope
#ContributesAndroidInjector(modules = {HomeActivityModule.class,
EwayBillFragmentProvider.class, EventBusModule.class})
abstract HomeActivity mainActivity();
#CustomScope
#ContributesAndroidInjector(modules =
{EwayBillDetailActivityModule.class, EventBusModule.class})
abstract EwayBillDetailActivity ewayBillDetailActivity();
}
these subcomponents are written inside ActivityBindingModule which is added to my application component. Now I want same instance of my PublishSubject object in both the subcomponents, I am fairly new to dagger and I want to know what am I doing wrong?
You'll need to move your bus into Application scope, which typically means annotating it with #Singleton (if that's how you've annotated your top-level component that ActivityBindingModule is installed into). You'll also need to move your method into a Module installed on that component, which might as well be ActivityBindingModule.
#Module
public abstract class ActivityBindingModule {
#Singleton
#Provides
public PublishSubject<Boolean> provideRxBus() {
// Dagger stores the instance in your Application component, so you don't have to.
return PublishSubject.create();
}
/* ... your #ContributesAndroidInjector Activity bindings remain here ... */
}
First, an explanation of what you see: #ContributesAndroidInjector creates a subcomponent for each object it annotates, marked with the scope annotations and modules you put on the #ContributesAndroidInjector method and annotation, so that your call to AndroidInjection.inject(this) in onCreate creates a new instance of that subcomponent and uses it to inject the Activity instance.
Your #CustomScope (which may be better-named as #ActivityScope here) on the #Provides PublishSubject<Boolean> method means that your instance will share the same lifecycle as the component that is also annotated with that scope annotation. Here, that's each automatically-generated subcomponent. Furthermore, because your Module is a non-abstract class with public no-arg constructor, Dagger will automatically create a new instance every time it creates a Component that requires your module, which means a different bus for each Activity instance. (It can't and won't do so for Modules that are abstract classes or interfaces.)
You want your bus object to be the same instance between Activities, which means that #CustomScope/#ActivityScope is much too short: You want the object to outlast any single Activity's lifecycle. This means that you'll either need to store the instance elsewhere and pass it into each Activity, or you'll need to store the instance in your Application component itself. I'd recommend the latter, because this is one of the problems Dagger was created to solve, and because this will automatically make the bus available across your application: Dagger subcomponents inherit access to all of the bindings in their parent components. That gives the code you see above. (Note that by doing this, you'll keep the instance of PublishSubject around even when there is no Activity showing, when your application is running in the background; if you want the same instance between Activities, this is a necessary consequence, but choose this carefully to avoid too much background memory use.)
One alternative is that you keep track of the bus instance yourself, and insert it into each Activity. You could do this by having your Module take a parameter, but that is rather tricky to do with dagger.android (which powers #ContributesAndroidInjector). You could also write a #Provides method that delegates to a WeakReference, or use the #Singleton technique above to write a holder that temporarily stores your bus between Activities. However, because Android keeps a lot of control over your transitions between Activities and the Activity lifecycle, it may be the best you can do to keep the bus in #Singleton scope as I did in the code above.
I'm trying to use android-dagger to inject a fragment from a manually defined subcomponent:
#Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
BuilderModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun someComponent(): SomeComponent
}
#Subcomponent
interface SomeComponent {
fun inject(fragment: SomeFragment)
}
Execution fails with:
IllegalArgumentException: No injector factory bound for Class "SomeFragment"
However, if I create a fragment bind annotated with #ContributesAndroidInjector it executes fine. The doc states that all this does is create a subcomponent. Why can't I do that manually?
Minimal working project can be found on github:
https://github.com/absimas/fragment-injection
#ContributesAndroidInjector creates a subcomponent, yes. The docs don't say anything more, but they don't assert that this only creates a subcomponent; it also installs it into the Map<Class, AndroidInjector.Factory> multibinding that powers each of dagger.android's injection types.
You can see an example of this map binding on the Android page of the Dagger User's Guide:
#Binds
#IntoMap
#FragmentKey(YourFragment.class)
abstract AndroidInjector.Factory<? extends Fragment>
bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
Note that this expects you to bind a Builder, not a Component: dagger.android expects that you'll want access to your Fragment instance from within your subcomponent, so the binding is for AndroidInjector.Factory<? extends Fragment> such that DispatchingAndroidInjector can call create(Fragment) on it. This is designed to be compatible with Subcomponent.Builder, but it does require that you define your #Subcomponent.Builder nested interface that extends the adapter AndroidInjector.Builder. Pardon my Java on a Kotlin question:
/** No need for an explicit inject, because you must extend AndroidInjector. */
#Subcomponent
interface SomeComponent extends AndroidInjector<SomeFragment> {
/**
* This abstract class handles the final create(Fragment) method,
* plus #BindsInstance.
*/
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<SomeFragment> {}
}
EDIT: It occurs to me now that your title states you don't want a dedicated subcomponent; beyond using a manual definition instead of #ContributesAndroidInjector, if you want a multi-Fragment subcomponent, then you might run into some trouble with this advice: dagger.android requires implementations of AndroidInjector<YourFragment>, and because of erasure, you own't be able to have a single class implement multiple AndroidInjector<T> or AndroidInjector.Builder<T> interfaces or abstract classes. In those cases you might need to write a manual AndroidInjector.Factory implementation which calls the correct concrete inject method. However, this seems like a lot of work for the sake of creating a monolithic Fragment component, when best-practices dictate dagger.android's default: a small and specific component for each Fragment.