Dagger2 not resolving dependency all the way

2019-03-05 10:47发布

问题:

I have a MainActivity which injects Presenter, presenter object injects interactor and interactor object injects APIHelper. All the providers of presenter, interactor and APIHelper are there in MainModule.

 @Module
public class MainActivityModule {
private final MainActivity activity;
//Context context;

public MainActivityModule (MainActivity activity) {
    this.activity = activity;
}



@Provides
@Singleton
public MainViewPresenter providesMainPresenter(){
    return new MainPresenterImpl(activity);
}

@Provides
@Singleton
ListingInteractor providesInteractor( ){
    return new ListingInteractorImpl(activity);
}

@Provides
@Singleton
ApiHelper providesAPI( ){
    return new ApiHelper(activity);
}

}

I have injected the component in the MainActivity like:

DaggerMainActivityComponent.builder()
                .mainActivityModule(new MainActivityModule(MainActivity.this))
                .build().inject(this);

In my Presenter implementation I have interactor like:

public class MainPresenterImpl implements MainViewPresenter {
      Context context;
    private MainView mainView;
   // @Inject
    ListingInteractor interactor;  //here i get null interactor
    MyScrollListener scrollListener;

    public MainPresenterImpl(MainActivity activity) {

        this.context = activity;
        this.mainView = activity;
    }
    @Override
    public void getCatFacts() {
        interactor.getFacts();
    }

My interactor implementation class has API helper whose constructor needs context

public class ListingInteractorImpl implements ListingInteractor{
    private Context context;
    @Inject
    private APIHelper; // getting null APIHelper



    public ListingInteractorImpl(Context context) {
        this.context = context;

    } 

My component interface is like:

@Component(modules={MainActivityModule.class})
@Singleton
public interface MainActivityComponent {



    void inject(MainActivity mainActivity);

    /*void inject(MainPresenterImpl presenter);*/



    MainViewPresenter getMainPresenter();

    ListingInteractor getInteractor();

    ApiHelper getHelper();
}

but only presenter object is created in the MainActivity all other objects in presenter including interactor, APIHelper are null.According to dagger it should resolve all dependencies.

回答1:

Dagger is not magic. It will not magically insert objects wherever you want unless you tell it to do so.

public class MainPresenterImpl implements MainViewPresenter {
  // ... other fields ...
  @Inject
  ListingInteractor interactor;

  public MainPresenterImpl(MainActivity activity) {
    this.context = activity;
    this.mainView = activity;
  }
}

To Dagger this is...nothing. You marked some field (ListingInteractor) for field injection, but unless you manually call a component to inject your object nothing will happen. Field injection should be reserved for Activities and Fragments where you can't add arguments to the constructor, not for your average classes.

@Provides
@Singleton
MainViewPresenter providesMainPresenter(){
  return new MainPresenterImpl(activity);
}

Instead of letting Dagger create MainPresenterImpl for you, you make a call to new MainPresenterImpl() yourself, only passing in the Activity. Since there is no call to MainPresenterImpl.interactor, it will be null. You're not using field injection, you're calling the constructor yourself and you're not assigning the field.
Manually creating objects in modules should be reserved for objects that require further setup, like Retrofit or OkHttp with their builders.

If you want your fields to be set, you could use field injection and register your objects with the Component (those inject(FieldInjectableClass clazz) methods) and sprinkle component.inject(myObject) throughout your code, which would be a really bad idea because you'd end up writing a lot of boilerplate that you don't need.

The more reasonable way is to move your dependencies to the constructor, where they belong.

public MainPresenterImpl(MainActivity activity, ListingInteractor interactor) { /* ... */ }

If you have a dependency on another class, why not declare it as such? But this still leaves the boilerplate of you creating the object yourself, instead of letting Dagger do its job.

That's why you should use Constructor Injection. As the name suggests, it's about the constructor. Just add the @Inject annotation:

@Inject // marked for constructor injection!
public MainPresenterImpl(MainActivity activity, ListingInteractor interactor) { /* ... */ }

Now Dagger knows about this entry point to your class and can create it.

To let dagger handle things you could add the @Singleton scope to the class itself (annotate the class, not the constructor) and just delete the @Provides method for it (there is no need for provides methods in modules for objects that don't need further setup), but since you're binding an Implementation to an Interface you still need to specify which class you want to bind to the interface.

@Provides
@Singleton
MainViewPresenter providesMainPresenter(MainPresenterImpl implementation){
  return implementation;
}

Since Dagger can create MainPresenterImpl with constructor injection you can return the implementation for your interface, and there is no need to update any code in case the constructor signature changes, as Dagger will just adapt the class instantiation accordingly.

That's how to use constructor injection and bind implemenations to interfaces. And as mentioned, I recommend highly to read up on the basics. Make sure you understand what Dagger does and how it works. Be sure to know the difference between field and constructor injection, or when to use modules.

The time you invest now in learning Dagger will mean much less debugging and errors later on.


In case that your module is abstract or an interface, you can also make use of the @Binds method, where Dagger will just generate the boilerplate code above.

@Binds
@Singleton
MainViewPresenter providesMainPresenter(MainPresenterImpl implementation);


回答2:

Change MainPresenterImpl & ListingInteractorImpl constructors to following and precede them with @Inject:


    @Inject
    public MainPresenterImpl(MainActivity activity, ListingInteractor interactor) {...}

    @Inject
    public ListingInteractorImpl(Context context, APIHelper helper) {...}

Then in your module implementation:


    @Provides
    @Singleton
    public MainViewPresenter providesMainPresenter(ListingInteractor interactor){
        return new MainPresenterImpl(activity, interactor);
    }

    @Provides
    @Singleton
    ListingInteractor providesInteractor(APIHelper helper){
        return new ListingInteractorImpl(activity, helper);
    }