Dagger 2.13: controlling scope of a constructor-injected class - android

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.

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.

Dagger 2 scope issue, define child fragment module. result: binding is not resolved for interface

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

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.

Bidirectional dependency with Dagger 2

I'm experimenting with VIPER architecture in my Android app. I use Dagger 2.11 for DI.
Dependencies I have for each VIPER module are:
Presenter and Fragment are linked via ViewInput and ViewOutput interfaces.
Presenter and Interactor are linked via InteractorInput and InteractorOutput interfaces.
Some other dependencies, we are not interested in.
This is how my Dagger Module looks like:
Module(includes = [Module.Declarations::class])
class Module(private val viewInput: ViewInput) {
#Module
interface Declarations {
#Binds
#FragmentLevel
fun bindViewOutput(viewOutput: Presenter): ViewOutput
#Binds
#FragmentLevel
fun bindInteractorOutput(interactorOutput: Presenter): InteractorOutput
#Binds
#FragmentLevel
fun bindInteractorInput(interactorInput: Interactor): InteractorInput
}
#Provides
#FragmentLevel
fun provideViewInput() = viewInput
}
Now when I call inject in my Fragment the following happens:
Presenter is injected into Fragment via ViewOutput interface
Interactor is injected into Presenter via InteractorInput interface
Presenter is not injected into Interactor, because of Lazy type (to prevent cyclic dependencies).
After the first call of Lazy.get() Presenter is injected into Interactor via InteractorOutput interface.
The problem is that in the 1st and 4th steps different instances of Presenter are injected. How can I make dagger inject the same presenter into Fragment and Interactor?
Or maybe I need to fix the dependency cycle in the other way?
The problem is fixed after adding #FragmentLevel scope to Presenter.

Multiple injection across same activity lifecycle results in multiple instances

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.

Categories

Resources