Multiple injection across same activity lifecycle results in multiple instances - android

There is this injector for the MainActivity defined as below in a module that is referenced by the ApplicationComponent:
#PerActivity
#ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity mainActivityInjector();
and the MainActivityModule referenced by the contributor looks like this:
#Module
public class MainActivityModule {
#Provides
#PerActivity
public MyActivityDependency myActivityDependency() {
return new MyActivityDependency();
}
}
and the MainActivity itself is:
public class MainActivity extends AppCompatActivity {
#Inject
MyActivityDependency myActivityDependency;
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
Log.d(myActivityDependency.hashCode());
AndroidInjection.inject(this);
Log.d(myActivityDependency.hashCode());
...
}
The #PerActivity Scope is supposed to preserve Activity’s dependency instances throughout its lifecycle.
This basically means that if I perform injection (AndroidInjection.inject(this)) multiple times, I am entitled to get the same injected instance (at least that's the goal).
In that case, why different instances of the MyDependency is injected each time the “.inject()” method is called?

The #PerActivity Scope is supposed to preserve Activity’s dependency instances throughout its lifecycle.
And it does. It creates an annotated dependency in a single component only once.
AndroidInjection is just a helper class that knows how to build the component for your Activity / Fragment. It does not store or persist it. Hence...
AndroidInjection.inject(this);
will create a new component every time it is called and then inject the dependencies. It is not supposed to be called multiple times, and why would you anyways? Just call it once in onCreate and everything will work fine.
In the case that you want to inject twice, you can inject the Activities component itself, and then use the component to inject again. Doing this, using the same component, you should get the same objects every time.
#Inject
DoubleInjectActivityComponent component;
Just inject it like you would any other dependency.

Related

Is Dagger component bound to the object that instantiate it?

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.

one object if injected into 2 subcomponents under same custom scope, every time new instance is created of that object

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.

Stuck attempting to implement the official Dagger strategy to avoid cumbersome code

In my efforts to follow the good and official advice for injecting and avoiding cumbersome code (which I had) from the authors themselves, I ran into a wall when trying to use the support library.
According to the article:
AppCompat users should continue to implement AndroidInjector.Factory<? extends Activity> and not <? extends AppCompatActivity> (or FragmentActivity).
I'm sticking to an MVP architecture where views are always Fragments and I don't want to involve my Activity in any DI business, but I wonder if it's necessary for this to work but so far I haven't been able to. If I skip the whole support thing, the app crashes at runtime because the instance of the fragment is support (in case it's not obvious). Then I went into the task of trying to try to implement HasSupportFragmentInjector instead of HasFragmentInjector with a whole bunch of changes due to compile errors my mind has forgotten for the sake of my mental health. After a while I come to a point of thinking how can a non-support Activity host a support fragment. Ah! Those tricky wildcards. But no matter how I've tried to follow the advice, I can't come up with a way without an EmptyModule that I also would need to setup in the Activity so it would be visible to the fragment by dagger and its (really, for me still, magic). Why I haven't tried it? I might as well have, but I'm tired of hopeless changes and I need help at this point.
AppModule.kt
#Singleton
#dagger.Module
class AppModule(val application: Application) {
#Provides #Singleton fun application(): Application = application
...
}
AppComponent.java
#ApplicationScope
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
...
FooFragmentModule.class,
})
public interface AppComponent {
Application app();
...
void inject(MyApp app);
}
MyApp.java
public class MyApp extends Application implements HasActivityInjector {
private AppComponent component;
public AppComponent someWayToReturnAppComponent() {
...
}
#Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
#Override
public void onCreate() {
component = DaggerAppComponent.builder()
.appModule(new AppModule(this))
// more app-scoped modules
.build();
component.inject(this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
}
MainActivity.java
public abstract class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout()); // inflate the fragment via XML here
}
#Inject DispatchingAndroidInjector<Fragment> dispatchingFragmentInjector;
#Override
public AndroidInjector<Fragment> fragmentInjector() {
return dispatchingFragmentInjector;
}
}
FooFragmentComponent.java
#Subcomponent
public interface FooFragmentComponent extends AndroidInjector<FooFragment> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<FooFragment> {}
}
FooFragmentModule.kt
#dagger.Module(subcomponents = {FooFragmentComponent.class})
public abstract class FooFragmentModule {
#Binds
#IntoMap
#FragmentKey(FooFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> bindFragmentInjectorFactory(FooFragmentComponent.Builder builder);
#ActivityScope
abstract FooFragment contributeFooFragmentInjector();
#Provides
static FooPresenter presenter() {
return new FooPresenter();
}
}
FooFragment
public class FooFragment extends Fragment implements SomeView {
#Inject FooPresenter presenter;
}
OK. At this point, and going back to
AppCompat users should continue to implement AndroidInjector.Factory<? extends Activity>
I've had no need (and willingly opposing) to use it, only for the fragment. Do I really need to setup a module and component for it or am I missing something?
EDIT
After following EpicPandaForce's advice of using AndroidSupportInjectionModule, Dagger now complains that
FragmentKey methods should bind dagger.android.AndroidInjector.Factory<? extends android.app.Fragment>, not dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>.
As #EpicPandaForce mentioned in the comments, you need to use AndroidSupportInjectionModule for support Fragments. You'll also need to use the FragmentKey in dagger.android.support, not the one in dagger.android. That should get you past the problem in your edit.
To your broader point, support Fragments do not extend base Fragments (which are deprecated anyway in API 28 and beyond). This paints them in contrast to AppCompatActivity and its superclass, the support library's FragmentActivity, which both extend the framework Activity as introduced in Android API level 1. Thus, whether you're using support Fragments or built-in Fragments, you might not have a parent AppCompatActivity, but you'll always have an Activity of some sort. This is important because Android reserves the right to instantiate your Fragment using its necessary public no-arg constructor, which means that the Fragment can only self-inject using things that it can find inside onAttach (i.e. its parent fragments, its Activity, or its Application).
dagger.android is unconcerned whether your Activity is an AppCompatActivity because it does not use the Activity other than looking for its own injector. You can see that in the AndroidSupportInjection.findHasFragmentInjector private method, which checks (in order) the hierarchy of parent fragments, then the Activity, then the Application. Consequently, even though practically speaking Support Fragments will only function properly on support Activities, dagger.android can bind its keys based on the superclass Activity because there's no reason to differentiate them and set up two separate maps (Activity vs AppCompatActivity). Even if there were a separation like that, you could bind AppCompatActivity injectors into your Activity map, and everything would get terribly confusing.
You should also take from that search order that if you do not have Activity-scoped bindings, you do not need to create an Activity-scoped component; you can have your Application implement HasSupportFragmentInjector, install your FooFragmentModule directly into AppComponent, and remove HasSupportFragmentInjector from your MainActivity. This is atypical only because most apps have some sense of Activity state or controllers that should be injectable (even just injecting the Activity instance itself, or its Context or Resources). If you only have your #ActivityScope annotation because you're trying to make this work, you can skip that step entirely and only use an Application component and several Fragment subcomponents. However, I think it is very likely that you will eventually need #ActivityScope, so creating a Component for it early-on is pretty reasonable.

Dagger 2.13: controlling scope of a constructor-injected class

I am using Dagger Android 2.13 and am in the process of setting up Activity-scoped dependencies.
I understand how to specify scope for dependencies declared inside a Module:
#Module
public class MyActivityModule {
#Provides
#PerActivity
MyActivityDataRepo provideMyActivityDataRepo() {
return MyActivityDataRepo(); // simplified for the sake of clarity
}
}
But how would I specify scope of a class added to dependencies graph via constructor injection such as below?
class MyActivityOtherDataRepo {
#Inject
MyActivityOtherDataRepo() {
}
}
Is there any way to make this class Activity Scoped for MyActivity?
Or will it be effectively Activity Scoped as soon as it's injected into MyActivity via member injection? And if so, is there a way to restrict scoping to MyActivity only? All I can think of to do so is to make MyActivityOtherDataRepo package private and place it in the same package as MyActivity.
You can scope an element by:
annotating the #Provides or #Binds annotated method with a scope
#Provides
#PerActivity
MyActivityDataRepo provideMyActivityDataRepo() { /*...*/}
or adding a scope annotation to the class itself with constructor injection
#PerActivity class MyActivityOtherDataRepo {
#Inject
MyActivityOtherDataRepo() { /*...*/}
}
It will be scoped by this scope, so any component within #PerActivity, as well as any subcomponents will be able to provide anything #PerActivity scoped.
The visibility of your class (public / package private) does not directly affect this scope, but of course you would not be able to import the class in other parts of your app.

Referencing the Activity inside its module

How do I use the new AndroidInjector.inject and still be able to provide an Activity instance inside an Activity Module? The Dagger docs don`t make it clear how to archive this.
The use case is the following: I have an Activity Module which provides a Presenter to my Activity, but the Presenter needs a reference to the Activity.
I used to have something like
#Inject Presenter presenter;
public onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((CustomApplication) getApplicationContext())
.getAppComponent()
.plus(new ActivityModule(this));
}
Can someone can point me to a sample that uses AndroidInjector.inject(this) instead and allow the reference of the Activity inside the Dagger 2 module?
Check Dagger 2 Github issue 615
The instance of your Activity is automatically provided, just pass it as a parameter in your module methods.
Example:
#Provides
#ActivityScope
public providePresenter(ActivityA activity) {
return new PresenterA(activity);
}
You'll now be able to abstract simple modules. Your presenter can be constructor injected too.
This actually cutout a lot of code from all my modules.

Categories

Resources