0
votes

I would like to have a fragment module which provides a view (fragment) and the presenter. View depends on the presenter, presenter depends on the view, and dagger takes care of those dependencies.

I've seen this method to work for activities and presenters, but when fail to apply this pattern to fragments. I would like to know if this is due to a mistake in my code, or is a limitation of dagger.

Here's the baseline code I use for activities:

Main Activity Contract

public interface MainActivityContract {
    interface View {
        void setText(String text);
    }

    interface Presenter {
        void attach();
    }
}

Main Activity Module

@Module
public abstract class MainActivityModule {

    @Binds
    abstract MainActivityContract.View exampleFragment(MainActivity view);

    @Binds
    abstract MainActivityContract.Presenter exampleFragmentPresenter(MainActivityPresenter presenter);
}

Activity Code:

public class MainActivity extends DaggerAppCompatActivity implements MainActivityContract.View {
    @Inject
    MainActivityContract.Presenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mPresenter.attach();
    }

    @Override
    public void setText(String text) {
        ((TextView) findViewById(R.id.example_text)).setText(text);
    }
}

Presenter Code:

public class MainActivityPresenter implements MainActivityContract.Presenter {

    private MainActivityContract.View mView;

    @Inject
    public MainActivityPresenter(MainActivityContract.View view) {
        mView = view;
    }

    public void attach() {
        this.mView.setText("Hello world!");
    }
}

Usual dagger-android setup:

Application Code

public class MyApplication extends DaggerApplication {
    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerAppComponent.builder().application(this).build();
    }
}

AppComponent

@Component(modules = {ActivityBindingModule.class, AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<MyApplication> {
    @Component.Builder
    interface Builder {
        @BindsInstance
        AppComponent.Builder application(Application application);

        AppComponent build();
    }
}

Activity Binding Module

@Module
public abstract class ActivityBindingModule {
    @ActivityScoped
    @ContributesAndroidInjector(modules = MainActivityModule.class)
    abstract MainActivity mainActivity();
}

Now I want to convert the activity into a fragment.

The above pattern is repeated, with the only difference in fragment code:

public class ExampleFragment extends DaggerFragment implements ExampleFragmentContract.View {
    @Inject
    ExampleFragmentContract.Presenter mPresenter;

    @Inject
    public ExampleFragment() {

    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.example_fragment, container, false);
    }

    @Override
    public void setText(String text) {
        ((TextView) getView().findViewById(R.id.example_text)).setText(text);
    }
}

Main Activity is dumbed down and the presenter is dropped:

public class MainActivity extends DaggerAppCompatActivity {
    @Inject
    ExampleFragment exampleFragment;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.root, exampleFragment);
    }
}

The module remains, but now injects the fragment:

@Module
public abstract class MainActivityModule {
    @FragmentScoped
    @ContributesAndroidInjector(modules = ExampleFragmentModule.class)
    abstract ExampleFragment exampleFragment();
}

The compilation now fails with an error:

Error:(13, 8) error: [dagger.android.AndroidInjector.inject(T)] com.example.michael.daggerfragmentexample.ui.ExampleFragment.ExampleFragmentContract.Presenter cannot be provided without an @Provides-annotated method.
com.example.michael.daggerfragmentexample.ui.ExampleFragment.ExampleFragmentContract.Presenter is injected at
com.example.michael.daggerfragmentexample.ui.ExampleFragment.ExampleFragment.mPresenter
com.example.michael.daggerfragmentexample.ui.ExampleFragment.ExampleFragment is injected at
com.example.michael.daggerfragmentexample.ui.MainActivity.MainActivity.exampleFragment
com.example.michael.daggerfragmentexample.ui.MainActivity.MainActivity is injected at
dagger.android.AndroidInjector.inject(arg0)
A binding with matching key exists in component: com.example.michael.daggerfragmentexample.ui.MainActivity.MainActivityModule_ExampleFragment.ExampleFragmentSubcomponent

The way I see it, I have two levels deep subcomponents and dagger ignores deeper module. Is this something I cannot do?

Link to the full sample code.

1
Show us your FragmentModule with binding or providing ExampleFragmentContract.PresenterSamuel Eminet

1 Answers

1
votes

From your ExampleFragmentPresenter you're missing scoping

@FragmentScoped
public class ExampleFragmentPresenter implements ExampleFragmentContract.Presenter

In addition you're trying to add your fragment scope injection into an activity scope from MainActivity

@Inject
ExampleFragment exampleFragment;

This is wrong since your activity scope knows nothing about your fragment injection so additionnaly do this :

public class MainActivity extends DaggerAppCompatActivity {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.root, ExampleFragment.newInstance());
    }
}

// removed @Inject here
public ExampleFragment() {

}