Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 389
R. Adang Avatar asked Apr 23 '15 13:04

R. Adang


People also ask

How do you get an interface in Dagger 2?

We can simply use @Inject at constructor! TL DR; If you have provide methods which just call constructor of implementation classes to inject interfaces, use @Binds annotation instead to get rid of boilerplate code in your dagger module.

What is the difference between Dagger and Dagger2?

x: It is an adaptation of an earlier version created by Square and now maintained by Google. Dagger2 is compile time Dependency injection framework that generates code to connect dependencies at compile time.

Is dagger deprecated?

It's officially deprecated and you can pretty much ignore it. Google's framework, which became dominant in Android ecosystem, was originally called Dagger 2. Sometimes we still refer to it as such, but, in most cases, we simply call it Dagger today.

How does a dagger work internally?

Dagger generates code similar to what you would have written manually. Internally, Dagger creates a graph of objects that it can reference to find the way to provide an instance of a class. For every class in the graph, Dagger generates a factory-type class that it uses internally to get instances of that type.


2 Answers

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.

like image 55
moskvax Avatar answered Sep 19 '22 04:09

moskvax


You could be able to inject your MainPresenterImpl using constructor injection.

/* unscoped */
public class MainPresenterImpl implements MainPresenter {

    @Inject 
    OkHttpClient client;

    @Inject
    public MainPresenterImpl() {
    }

    @Override public void doStuff() {
       // this.client is now available! :)
    }
}


@Module 
public class AppModule {
    private MyApplication application;

    public AppModule(MyApplication application) {
        this.application = application;
    }

    @Provides
    /* unscoped */ 
    public MyApplication application() {
        return application;
    }
}

@Module 
public abstract class MainActivityModule {
    @Binds public abstract MainPresenter mainPresenter(MainPresenterImpl mainPresenterImpl);
}
like image 29
EpicPandaForce Avatar answered Sep 18 '22 04:09

EpicPandaForce