Reputation: 2049
I am working with MVP and Dagger 2 DI. I have a Fragment that I reuse in a few activities. I have an interface type for presenter as a property of the Fragment, say MVPPresenter. Depending in which activity the Fragment is being used, I need to inject different presenters into it (each presenter is an implementation of MVPPresenter). So I need a way to inject each implementation of MVPPresenter into the Fragment as I need.
Currently, I have a terrible solution, which works, but it is simply wrong and creates unnecessary objects that are never used. Here is the code:
public class MyFragment {
...
@Inject
public void setPresenter(@NonNull ProfilePresenter presenter) {
if (mAdapter instanceof ProfileAdapter) {
this.presenter = presenter;
}
}
@Inject
public void setPresenter(@NonNull ContactsPresenter presenter) {
if (mAdapter instanceof ContactsAdapter) {
this.presenter = presenter;
}
}
...
}
Here is my Module:
@Module
class PresentersModule {
@Provides
@Singleton
ProfilePresenter ProfilePresenter() {
return new ProfilePresenter();
}
@Provides
@Singleton
ContactsPresenter ContactsPresenter() {
return new ContactsPresenter();
}
}
You see, depending on Adapter type, I assign presenter, or do not. I know this is stupid and all. Problem is that Dagger needs exact type to inject to be specified and Interface type wont work. What is the proper way of dealing with such cases?
Upvotes: 4
Views: 4967
Reputation: 95704
You have, as I see it, three solutions of varying degrees of weight.
Inject two choices as you have now: If you know all of your Fragment's use-cases up front, and you don't need to vary the dependency graphs any more than on a single class, you can do so easily using a similar method to what you have now. My variant uses Providers, which are bound automatically for any object in your graph, so that you don't unnecessarily create whole trees of objects; also, @Inject methods can take an arbitrary parameter list, so you can do all of your method injection in one method if you choose.
@Inject
public void setPresenter(
@NonNull Provider<ContactsPresenter> contactsPresenterProvider,
@NonNull Provider<ProfilePresenter> profilePresenterProvider) {
if (mAdapter instanceof ContactsAdapter) {
this.presenter = contactsPresenterProvider.get();
} else if (mAdapter instanceof ProfileAdapter) {
this.presenter = profilePresenterProvider.get();
}
}
The other two solutions involve multiple components: Instead of saying "there is one way of binding my graph together", you're effectively asking Dagger to generate multiple options for you, which means that your graphs can vary widely but stay consistent. This technique might be more useful if you reuse objects in different ways for different sections of your application, like if you have a Profile section and a Contacts section, each of which using a common A injecting a common B injecting a common C injecting a different D. To consistently support two deep graphs like that, child components are a much better option.
Use component dependencies: As in rst's answer, you can use component dependencies to isolate your fragments. They did a pretty good job of explaining, so I'll not repeat that here. You should be aware, though, that component dependencies can only consume bindings that are exposed on the component you depend on: Even if Foo and Bar are bound on DiComponent, you won't be able to access them from your ProfileComponent or ContactsComponent unless you put Foo getFoo()
and Bar getBar()
on your DiComponent. (That said, component dependencies don't have to be Dagger components, either; they can be arbitrary types that you implement yourself or let Dagger implement for you.)
Use subcomponents: Though rst alluded to subcomponents, I think they warrant a bit more explaining, particularly because they are a core component of the recently-released dagger.android functionality, and because Fragments and other UI pieces can be difficult to extract with component dependencies—subcomponents implicitly and automatically inherit bindings from the surrounding component, so you don't have to explicitly expose bindings on your DiComponent. See other differences at this SO question.
@Component
public interface DiComponent {
ProfileComponent getProfileComponent(); // Dagger generates implementations
ContactsComponent getContactsComponent(); // as part of DiComponent.
}
@Subcomponent(modules={ContactsModule.class})
public interface ContactsComponent {
void inject(MyFragment myFragment);
}
@Module
public interface ContactsModule {
@Binds MvpPresenter bindMvpPresenter(ContactsPresenter contactsPresenter);
}
@Subcomponent(modules={ProfileModule.class})
public interface ProfileComponent {
void inject(MyFragment myFragment);
}
@Module
public interface ProfileModule {
@Binds MvpPresenter bindMvpPresenter(ProfilePresenter profilePresenter);
}
In the above, the root DiComponent doesn't have a binding for MvpPresenter, so in itself it can't inject MyFragment. However, ProfileComponent and ContactsComponent can, and each will use different graphs configured in the corresponding Modules (but silently inheriting common bindings from DiComponent's modules). If the graphs vary differently further down, like with each MvpPresenter using the same Validator but with a different ProfileValidationRule versus ContactsValidationRule, you could bind ValidationRule to those different classes in your different Modules to get different behavior.
(For completeness, you would usually also have the option to use a factory like AutoFactory
and pass in a parameter like the presenter to your specific container like Fragment. However, this is only really an option if you're creating your instances, and not really an option when Android forces a zero-arg public constructor so it can create Fragment instances at will.)
Upvotes: 4
Reputation: 46
Looking through the names you've given to mvp-presenters, one could conclude, their complementary mvp-views should rather be separated and implemented in different fragments.
But if you wish to maintain things as-is, having only single setPresenter
method declared in your fragment, probably the easiest way to deal with your problem would be to introduce separate components with complementary modules for providing desirable presenter implementations.
For this solution to work you would need to adjust your fragment to contain single declaration of setPresenter
method with MVPPresenter
type as an argument:
@Inject
public void setPresenter(@NonNull MVPPresenter presenter) {
this.presenter = presenter;
}
Afterwards, you'd need to provide components exposing inject(...)
method and declaring usage of appropriate module. As those dependency graphs would be dependent on main component instance, they should get their own scope (tied to activity or fragment, depending on what class is actually holding the graph object).
For instance, if you were using DiComponent
for providing all your dependencies with scope defined via @Singleton
annotation, you'd need to declare @MyFragmentScope
annotation and provide components, dependent on above-mentioned DiComponent
, in order to declare injectable presenters:
import javax.inject.Scope;
@Scope
public @interface MyFragmentScope {
}
Your dependent components would look like:
@MyFragmentScope
@Component(dependencies = DiComponent.class, modules = ProfileModule.class)
public interface ProfileComponent {
void inject(MyFragment fragment);
}
with complementary module:
@Module
public class ProfileModule {
@Provides
@MyFragmentScope
MVPPresenter providesProfilePresenter() {
return new ProfilePresenter();
}
}
Note: return type is MVPPresenter
, not concrete implementation.
Similarly you'd need to create ContactsComponent
and ContactsModule
for your ContactsPresenter
.
Eventually you should use proper component instance to perform the injection. Now instead of using
diComponent.inject(myFragment)
you should use component which would provide desirable dependency.
At this point you would actually have a switch defining which presenter should be used.
In case of ProfilePresenter
injecting you'd need to use:
DaggerProfileComponent.builder()
.diComponent(diComponent)
.build()
.inject(myFragment);
Or in case of ContactsPresenter
injecting you'd need to use:
DaggerContactsComponent.builder()
.diComponent(diComponent)
.build()
.inject(myFragment);
It's rather common practice to use separate components for smaller parts of application like activities. It's possible to either declare such components as regular dependent ones or as sub components (see @Subcomponent
documentation for reference). Starting from Dagger 2.7 there is a new way of declaring Subcomponents via @Module.subcomponents
. Due to this fact there's an opportunity to decouple AppComponent from Activities Subcomponents. You may refer to sample GitHub repository from frogermcs for reference. He also has a great complementary blog post on this topic.
Upvotes: 1