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.
Related
Without using FragmentFactory, scoping dependencies within Fragment is straigth forward. Just create a Subcomponent for your fragment and just create the Fragment's Subcomponent in onAttach().
But when using FragmentFactory you no longer inject dependencies via the subcomponent but instead pass then in Fragment's constructor.
I was wondering if I could still declare a dependency which should only last within the fragment's lifecycle using Dagger. I currently could not think of a way to achieve this.
So instead of binding my dependencies to a certain scope, I just declare the dependency with any scope or just use #Reusable on them.
Also, since fragments are created through FragmentFactory, the created Fragments doesn't exist in the DI graph.
How can we properly scope dependencies to fragment and also be able to add the fragment in the DI graph when using FragmentFactory?
You can accomplish this by making a Subcomponent responsible for creating your Fragment. The Fragment should have the same scope as this Subcomponent.
#Subcomponent(modules = [/* ... */])
#FragmentScope
interface FooSubcomponent {
fun fooFragment(): FooFragment
#Subcomponent.Factory
interface Factory {
fun create(): FooSubcomponent
}
}
After taking care of any cyclic dependency issues, this Subcomponent acts the same as if you had explicitly created a subcomponent in onAttach() using #BindsInstance, except that you can (and must) now use constructor injection on FooFragment.
In order to provide FooFragment in your main component (or parent subcomponent), you will need to install this Subcomponent into a module.
#Module(subcomponents = [FooSubcomponent::class])
object MyModule {
#Provides
#IntoMap
#FragmentKey(FooFragment::class)
fun provideFooFragment(factory: FooSubcomponent.Factory): Fragment {
return factory.create().fooFragment()
}
}
Some caveats:
The scenario you describe (FooFragment depends on some class which depends on FooFragment) is the definition of a cyclic dependency. You will need to inject a Lazy or Provider at some point in this cycle, or Dagger will throw an error at compile time.
If you split this up into two steps, first providing FooFragment and then binding it into your map, the subcomponent will see the #Provides method from its parent component. This is likely to cause a StackOverflowError if you aren't careful.
I am following architecture-components-samples: GithubBrowserSample
I have a Viewmodel that has a dependency and is provided through constructor injection, Since App component is singleton dagger forces me to use Singleton scope only if I use any other scope for those components I get below error
com.example.AppComponent scoped with #Singleton may not reference bindings with different scopes:
I tried this as well but still same
#Binds
#IntoMap
#ViewModelKey(MyViewModel::class)
#PerActivity
abstract fun bindMyViewModel(myViewModel: MyViewModel): ViewModel
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.
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.
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.