8

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

Joeleski
  • 459
  • 4
  • 11
  • Have you considered manually using component provision method or `@Named` annotation? – EpicPandaForce Sep 12 '16 at 06:45
  • EpicPandaForce, @Named by itself doesn't work as the dependant object doesn't know which implementation it needs. What do you mean by component provision method? – Joeleski Sep 12 '16 at 07:15

3 Answers3

5

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();
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
2

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.)

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Sorry for my ignorance, but let's say if I go with option 1, when a button is clicked, I want to swap implementation of `FooInterface`, I would have to recreate the whole `Component` that provide `FooInterface`, wouldn't I? if so, it sounds kind of expensive thing to do... Also, how could I keep other dependencies' state in the `Component` that has been recreated? – Leo Apr 18 '17 at 00:39
  • @Leo The original question asks about a constant known at module creation time, so yes that's what you'd have to do for the above. If you'd like to return a different value from the same component, you can either (1) change the value in the Module after the Module has been created, (2) have your `@Provides` method inject a value or value-holder that tells you which is the right thing to inject, or (3) you can inject a factory [like this Guice equivalent](http://stackoverflow.com/a/24071727/1426891) instead. I'd favor #3, as it gets tricky to change injectable bindings after component creation. – Jeff Bowman Apr 18 '17 at 01:16
  • ah, sorry, I missed that (I googled swapping implementation at run time then I got here). Anyway, if I understand correctly, in (1) and (2) I have to recreate the `Component` and re-inject wherever `FooInterface` is used. For (3), I think recreating and re-injecting is not required, but I'll have to provide all implementations of `FooInterface` upfront, is that a good idea if I use `Lazy` different implementations into the factory object? – Leo Apr 18 '17 at 02:30
  • @Leo Don't inject a `Lazy`, inject a `Provider` instead (as in my link). That way you neither _pre-create_ nor _save your object for later_, as injecting a `T` instance or a `Lazy` would do respectively. – Jeff Bowman Apr 18 '17 at 03:02
  • By using `Lazy` I just intended to eliminate pre-creating injections. I don't get latter one though, what do you mean by *save your object for later* and what's wrong with that? – Leo Apr 18 '17 at 04:20
  • Ah, I get it, `Lazy` always returns same instance of `T`, but still, we're injecting concrete implementations of the interface to the provider, so isn't that better to always return same instance of each implementation? – Leo Apr 18 '17 at 04:25
  • @Leo That may be the case for _your_ specific problem _today_, but in general you should give yourself the flexibility of returning a new or existing instance. If it's important to return the same instance, you can always do so using [scoping](https://google.github.io/dagger/users-guide.html#singletons-and-scoped-bindings) instead, which better separates the implementation-switching behavior from the object-lifetime behavior. – Jeff Bowman Apr 18 '17 at 06:12
  • True, I thought about `@Singleton` as well. Btw, you are right, sorry for asking so specific about my "today" problem and thank you for the answers, it helped :) – Leo Apr 18 '17 at 06:21
-4

Have you tried something like this?

public class hectic extends Iam {

    String tokenizer heccas = new string tokenizer();
}
Draken
  • 3,134
  • 13
  • 34
  • 54