Search code examples
androidandroid-fragmentsdagger-2android-mvp

Android | Dagger 2. Injecting different subclasses into Fragment depending of a condition


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?


Solution

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