Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I tell Dagger 2 which implementation to instantiate based on X?

Tags:

java

dagger-2

Inside a module, if I need to provide a different implementation of an interface based on a variable known at module construction time I can put the logic inside the @Provides method for that interface type. Like so:

@Module
public class FooModule {

    private final State state;

    public FooModule(State state) {
        this.state = state;
    }

    @Provides
    FooInterface provideFooImplementation() {
        switch(state) {
            case STATE_1:
                return new FooImpl1();
            case STATE_2:
                return new FooImpl2();
            ...
            case STATE_10:
                return new FooImpl10();
        }
    }
}

However, these implementations could be created by Dagger. I would rather say "Hey, based on X I want you to instantiate this class for me"

I have considered a couple of options.

  1. Change the provides method to take in all of the possible implementations:

    @Provides
    FooInterface provideFooImplementation(FooImpl1 impl1, FooImpl2 imp2, ..., FooImpl10 impl10) {
        switch(state) {
            case STATE_1:
                return impl1;
            case STATE_2:
                return impl2;
            ...
            case STATE_10:
                return impl10;
        }
    }
    

This allows Dagger to instantiate them and satisfy all of their dependencies but it's not a good idea if each of the implementations is relatively large or expensive to create.

  1. Change the provides method to take in a collection of all of the dependencies for the different implementations.

    @Provides
    FooInterface provideFooImplementation(Context context, Repo repo, HttpClient httpClient, ...) {
        switch(state) {
            case STATE_1:
                return new FooImpl1(context);
            case STATE_2:
                return new FooImpl2(repo, httpClient);
            ...
            case STATE_10:
                return new FooImpl10(context, repo);
        }
    }
    

This is slightly better than option 1 in that Dagger doesn't have to instantiate every single implementation, however it still needs to instantiate all of the dependencies even though they might not be used in all cases. I'm also back to creating the objects myself even though they could be created by Dagger.

  1. Create one module per implementation and instantiate the appropriate module. So something like:

    @Module
    public FooImpl1Module {
    
        @Provides
        FooInterface provideFooImplementation(Context context) {
            return new FooImpl1(context);
        }
    }
    

This would be fine, but now I have issues defining the component that depends on the module.

What's the best way to tackle this problem?

One suggestion has been to try option 1 with the parameters wrapped in Lazy. Then I only end up calling .get() on one. I'll try this out when I can and post the results

like image 381
Joeleski Avatar asked Sep 12 '16 06:09

Joeleski


2 Answers

A possible solution would be using @Named("foo") annotation in conjunction with favoring component provision method over manual injection, which would however mean that your state would be independent from the module itself, and you'd be the one to make your choice

@Component(modules={FooModule.class})
public interface AppComponent {
    @Named("STATE_1")
    FooInterface fooImpl1();
    @Named("STATE_2")
    FooInterface fooImpl2();
    ...
    @Named("STATE_10")
    FooInterface fooImpl10();
}

@Module
public FooImpl1Module {
    @Provides
    @Named("STATE_1")
    FooInterface provideFooImpl1(Context context) {
        return new FooImpl1(context);
    }

    @Provides
    @Named("STATE_2")
    FooInterface provideFooImpl2(Context context) {
        return new FooImpl2(context);
    }

    ...

    @Provides
    @Named("STATE_10")
    FooInterface provideFooImpl10(Context context) {
        return new FooImpl10(context);
    }
}

Then you can call

FooInterface fooInterface = component.fooImpl1();
like image 74
EpicPandaForce Avatar answered Nov 15 '22 00:11

EpicPandaForce


Rather than Lazy<T>, do option 1 with Provider<T>. Lazy<T> is just a Provider<T> that memoizes locally (with the necessary double-checked locking), but because you know you'll only call one Provider exactly once, you can just inject the Provider instead and skip the synchronization overhead.

@Provides
FooInterface provideFooImplementation(
        Provider<FooImpl1> impl1,
        Provider<FooImpl2> impl2,
        ...,
        Provider<FooImpl10> impl10) {
    switch(state) {
        case STATE_1:
            return impl1.get();
        case STATE_2:
            return impl2.get();
        ...
        case STATE_10:
            return impl10.get();
    }
}

Option 2 will work, but you'll effectively skip the dependency wiring that Dagger could easily do for you, and Option 3 won't work as stated because your @Component annotation needs your list of modules to be a compile-time constant for your Dagger code generation to work.

(A variant of Option 3 could work if your binding were to a constant or a zero-dependency class of one form or another, because then you could pass in an arbitrary subclass of your Module into your Component builder. However, Dagger can only analyze the bindings in the superclass module, and you'd have trouble if your @Provides method implementations take different parameters as yours would, so the switch is the best and clearest alternative I can think of.)

like image 21
Jeff Bowman Avatar answered Nov 15 '22 01:11

Jeff Bowman