4

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?

Sermilion
  • 1,920
  • 3
  • 35
  • 54
  • Do you use the same component per Activity, or different components? It seems like the ideal is to bind a different Presenter for each Activity and then inject the interface into the Fragment, which is easy to do if you use a different component (subcomponent) per Activity. – Jeff Bowman Mar 14 '17 at 22:21
  • Actually, I am using same DiComponent where I have method declaration for each class where I inject dependencies. I don't fully understand what you mean. If you could provide a code example, that would be great. – Sermilion Mar 15 '17 at 05:45

2 Answers2

4

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.)

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
1

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.

rst
  • 36
  • 2
  • 1
    There's no need for separate scopes unless you're trying to keep the same objects injected multiple times without being recreated, so Fragment scope (though generally useful) is unnecessary here. I also disagree that these should be separate Fragments—as long as there's enough state kept so Android can restore instances arbitrarily, I think it makes plenty of sense to have one Fragment shared between the two cases. – Jeff Bowman Mar 15 '17 at 09:14
  • Totally agree on scopes point. I just made some assumptions based on my own experience, but I rather should have asked the topic starter for details on actual MVP implementation, especially details on where are presenters instantiated (which scope), whether they are stateless etc. – rst Mar 15 '17 at 09:26
  • Great answer. After reading an article on Medium, I too, came to conclusion that I have to use separate DiComponents. Very nice answer. I ll test it later. About presenters: they are injected into fragment in the same way with @Singleton scope. I am using Mosbey (I am not sure if they are stateless, I am not sure what that means, but they are retained on configuration change and preserve their state, so I guess they are stateful?). Is there anything else to add based on provided information? Maybe a better way of dealing with dagger injections or "my way" is OK? Thank you. – Sermilion Mar 15 '17 at 12:10
  • You should pay attention to the fact that with given solution you'd be using another dependency graph object for injecting dependencies (e.g. `DaggerContactsComponent. ... .build()`) which is dependent and reuses dependencies from your DiComponent instance. That means dependencies provided by DiComponent are still @Singleton-scoped. If you do set scope (like `@MyFragmentScope`) on your presenter providers, than you are guaranteed to get the same instance of given presenter if you query the very same instance of dependency graph (component). – rst Mar 15 '17 at 13:56
  • e.g. if you create few component instances of the same type (e.g. invoke `DaggerContactsComponent. ... .build()` few times), each produced instance would contain its own "singleton" of presenter. Having this said, you should now decide which place should you instantiate your component at: either you create instance of it inside `Activity#onCreate(Bundle)` or inside `Fragment#onCreate(Bundle)`, you should be aware of the fact that during Activity recreation your component would be created from scratch, thus, another instance of presenter would be returned upon activity/fragment re-creation. – rst Mar 15 '17 at 13:57
  • It's actually no problem if your presenters are stateless. But if, for example, your presenter is executing network request, and you (as a user) rotate screen causing activity re-creation, you eventually end up with a new instance of presenter used by your view, and your view now reflects state directed by the new presenter, while the fact of request execution/completion in previously used presenter is essentially ignored. That's why I prefer stateless presenters, which just subscribe to the changes in model layer and/or delegate some actions to model if requested. – rst Mar 15 '17 at 13:57
  • Regarding single fragment - the main point meant of "reusability" is that the same fragment type may be reused in different activities. If you find your logical views can be implemented using single fragment that's your decision. But what about view->presenter communication in such scenario? Don't your views invoke actions upon presenter? If your presenters utilize different subset of accessible methods, you'll end up casting your presenter to desired type in order to invoke needed action. The decision is up to you, as you grasp whole the picture for your case. – rst Mar 15 '17 at 13:57
  • Very comprehensive explanation. Thanks a lot. Everything works now. – Sermilion Mar 15 '17 at 14:53