0

I'm trying to support third party extensions using map multibindings, but I can't wrap my head around how it's supposed to provide an extensible infrastructure. It seems like it should be really simple, but the Dagger 2 documentation on multibindings doesn't actually provide an example of how to do it. I want other developers to be able to write their own implementations of an interface that I provide, and then have that seamlessly integrate.

Here's some sample code that shows what I'm attempting to do:

// This is the interface that third parties can implement.
public interface FooService {
  public void run();
}

// This is the default implementation of the interface that I provide.
class DefaultFooImpl implements FooService {
  DefaultFooImpl() { ... }
  @Override public void run() { ... }
}

// Third parties would need to add their own modules to provide their
// implementations of FooService on different keys (right?).
@Module class DefaultImplModule {
  @Provides(type = MAP)
  @StringKey("default")
  static FooService provideDefaultImpl() {
    return new DefaultFooImpl();
  }
}

// PROBLEM! This won't work for third-party implementations, since I
// can't include their modules here because I don't know them.
@Component(modules = DefaultImplModule.class)
interface FooServiceComponents {
  Map<String, FooService> fooServices();
}

public class FooDispatcher {
  // PROBLEM! How do I actually inject this map?  Does this work?
  @Inject Map<String, FooService> fooServices;

  void callFooService(String whichService) {
    // whichService is any of the Strings in the combined services map.
    // For the default implementation, you'd pass in "default".
    this.fooServices.get(whichService).run();
  }
}

So what's the missing piece that ties this all together and actually makes it work? Thanks.

Cyde Weys
  • 154
  • 2
  • 7
  • To answer your question, "How do I actually inject this map? Does this work?": yes. – philo Sep 20 '16 at 18:21
  • Your code looks right. Are you asking how you would inject more entries in the map in a decoupled way? You can inject entries in any module in the application. The consumer of the map doesn't have to reference them, but the application needs to install the modules (just like any other dependency). – philo Sep 20 '16 at 18:31

1 Answers1

1

Here is the pattern I use - the Module will have to be configured at time of using the Component builder by passing your arguments. Strongly recommend sparing usage and only if needed - things should be as simple as you need them to be, but not any more :-)

@Module
class ConfigurableModule {

    private IDependencyOne one;
    private IDependencyTwo two;
    ...

    ConfigurableModule() {

    }

    ConfigurableModule(IDependencyOne one, IDependencyTwo two, ...) {
        this.one = one;
        this.two = two;
    }


    @Provides IDependencyOne getIDependencyOne(MyIDependencyOneImpl impl) {
        return one == null ? impl : one;
    }

    @Provides IDependencyTwo getIDependencyTwo(MyIDependencyTwoImpl impl) {
        return one == null ? impl : one;
    }
}
Angad
  • 2,803
  • 3
  • 32
  • 45