neonDion neonDion - 16 days ago 9
Android Question

Dagger 2 vs Injector class

I'm new to using Dagger 2 and am wondering what its advantages are over a technique that I currently use to achieve dependency injection.

Currently to achieve DI I create a project with two flavors, mock and prod. Within these flavors I create a class called Injector.

//prod Injector
public class Injector {
public static INetworkLayer provideNetworkLayer() {
return new ProductionNetworkLayer();
}
}

//mock Injector
public class Injector {
public static INetworkLayer provideNetworkLayer() {
return new MockNetworkLayer();
}
}


The Injector class has a static method for each object I need to inject. In my application code I can then simply write:

INetworkLayer networkLayer = Injector.provideNetworkLayer();


This works great for testing because in my production code or test code I can simply tell the Injector what I want and depending on what build variant I am using, the Injector will give me either the production object or the mocked test object. If I want a certain object to be a singleton I am simply able to hold a reference to it in the Injector class and then give that reference out when provide...() is called.

I have started using Dagger2 in a new project and have found that it is much more confusing to setup mock / prod dependencies compared to the "Injector" class method that I demonstrated above.

How is what Dagger 2 does different than the "Injector class" method?

Is there a good way to use Dagger 2 to provide mock and prod classes?

Answer

am wondering what its advantages are over a technique that I currently use to achieve dependency injection.

Automatic dependency graph resolution, and scoped providers via adding a scope annotation.


But your example can easily be converted into one that can be used with Dagger2, look:

//prod Injector
@Module
public class NetworkModule {
    @Provides
    @Singleton
    public static INetworkLayer provideNetworkLayer() {
        return new ProductionNetworkLayer();
    }
}

//mock Injector
@Module
public class NetworkModule {
    @Provides
    @Singleton
    public static INetworkLayer provideNetworkLayer() {
        return new MockNetworkLayer();
    }
}

And

@Singleton
@Component(modules={NetworkModule.class})
public interface SingletonComponent {
    INetworkLayer provideNetworkLayer();
}

And

public class Injector {
    private Injector() {
    }

    private static SingletonComponent singletonComponent;

    static {
        singletonComponent = DaggerSingletonComponent.create();
    }

    public static SingletonComponent get() {
        return singletonComponent;
    }
}

Because then you can do:

INetworkLayer networkLayer = Injector.get().provideNetworkLayer();


Now you can ask, "wait a second, I already did that but now it takes more code to set up!"

And that's because you never really showed what happens in case your dependencies depend on each other.

For example,

@Module
public class NetworkModule {
    @Provides
    @Singleton
    public static INetworkLayer provideNetworkLayer(OkHttpClient okHttpClient) {
        return new ProductionNetworkLayer(okHttpClient);
    }

    @Provides
    @Singleton
    public OkHttpClient provideOkHttpClient() {
        return new /*create okHttpClient*/;
    }
}

And also, you can actually use @Inject annotation to simplify your modules to a certain degree. Although when using interfaces, it's not as evident.

@Singleton
public class ProductionNetworkLayer {
    private OkHttpClient okHttpClient;

    @Inject
    public ProductionNetworkLayer(OkHttpClient okHttpClient) {
         this.okHttpClient = okHttpClient;
    }
}

@Module
public abstract class NetworkModule {
    @Binds
    public abstract INetworkLayer provideNetworkLayer(ProductionNetworkLayer productionNetworkLayer);
    // same as `public INetworkLayer prov(ProductionNetworkLayer prod) { return prod; }`

    @Provides
    @Singleton
    public OkHttpClient provideOkHttpClient() {
        return new /*create okHttpClient*/;
    }
}