R. Adang R. Adang - 10 days ago 5
Android Question

Dagger 2 and interface implementations

I've got a simple Dagger 2 test-setup, based on http://konmik.github.io/snorkeling-with-dagger-2.html.
It injects a PreferenceLogger which outputs all the preferences. In the injected class, I can @Inject more classes.

public class MainActivity extends Activity {
@Inject PreferencesLogger logger;
@Inject MainPresenter presenter;

@Override protected void onCreate(Bundle savedInstanceState) {
MyApplication.getComponent().inject(this);
presenter.doStuff();
logger.log(this);
}
}


public class PreferencesLogger {

@Inject OkHttpClient client;
@Inject public PreferencesLogger() {}

public void log(Contect context) {
// this.client is available
}
}


When I run this, logger is set, and inside PreferencesLogger.log the OkHttpClient is correctly set.
So this example works as expected.
Now I'm trying to get a MVP structure in place.
There's a MainPresenter interface with an implementation. In the MainActivity I set an:

@Inject MainPresenter presenter;


so I could switch this MainPresenter with an alternative (debug or test) implementation. Ofcourse, now I need a Module to specify what implementation I want to use.

public interface MainPresenter {
void doStuff();
}

public class MainPresenterImpl implements MainPresenter {

@Inject OkHttpClient client;

public MainPresenterImpl() {}

@Override public void doStuff() {
// this.client is not available
}
}


@Module public class MainActivityModule {
@Provides MainPresenter provideMainPresenter() {
return new MainPresenterImpl();
}
}


A problem now occurs that the OkHttpClient isn't injected anymore. Ofcourse I could alter the Module to accept a parameter OkHttpClient, but I don't think this is the suggested way to do it. Is there a reason why the MainPresenterImpl doesn't Inject correctly?

Answer

Unlike with constructor injection, the @Inject annotated fields of dependencies constructed in @Provides methods can't be automatically injected. Being able to inject fields requires a component that provides the type of the field in its modules, and in the provider methods themselves, such an implementation is not available.

When the presenter field is injected in MainActivity, all that happens is the provider method is called and presenter is set to its return value. In your example, the no-args constructor does no initialization, and neither does the provider method, so no initialization takes place.

The provider method does however have access to instances of other types provided in the module via its parameters. I think using parameters in the provider method is in fact the suggested (even the only) way to "inject" the dependencies of the provided type, because it explicitly indicates them as dependencies within the module, which allows Dagger to throw an error at compile-time if they can't be satisfied.

The reason it doesn't currently throw an error is because MainPresenterImpl could get its OkHttpClient dependency satisfied if MainPresenterImpl and not MainPresenter was somewhere a target for injection. Dagger can't make a members-injection method for the interface type, because as an interface, it can't have injectable fields, and it won't automatically inject the fields of the implementing type, because it's just supplying whatever the provider method returns.