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?