Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guice/Gin. How to inject multiple implementations

Tags:

gwt

guice

gwt-gin

I have a webapp that use GIN to inject dependencies at entry point.

private InjectorService injector = GWT.create(InjectorService.class);

@GinModules({PlaceContollerInject.class, RootViewInject.class})
public interface InjectorService extends Ginjector {

  RootView getRootView();
  PlaceController getPlaceConroller();

}

public class RootViewInject extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(RootView.class).to(RootViewImpl.class);
  }
}

I need a mobile version that use different RootView implementation. Dependencies are described in the following module

public class RootViewMobileInject extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(RootView.class).to(RootViewMobileImpl.class);
  }
}

The question is how to choose needed dependency conditionally whether we need mobile or default version. I've seen GWT-GIN Multiple Implementations, but haven't figured out that solution because the Provider breaks the dependencies' chain and the Factory Pattern breaks testability. In "Big Modular Java with Guice" video here (12 minute) Guice's injector with modules was presented as a replacement to Factories. So my question is should I create different Ginjector for mobile and default versions (like MobileFactory and DefaultFactory) of my app or it would be bad practice and I should configure one instance of Ginjector with all needed versions. For example with the annotation bindings like this.

public class RootViewMobileInject extends AbstractGinModule {

  @Override
  protected void configure() {
    bind(RootView.class).annotatedWith(Mobile.class).to(RootViewMobileImpl.class);
  }
}

and use @Mobile annotated bindings at GWT entry point

  @Inject
  private void setMobileRootView(@Mobile RootView rw) {
    this.rw = rw;
  }

In such a simplified example as above it might be possible. But if an application have more dependencies which need mobile and default versions. It looks like back to untestable "ugly" (as at Guice's presentation was said) factories. Sorry for my English. Any help is appreciated.

like image 955
trupanka Avatar asked Dec 04 '11 22:12

trupanka


2 Answers

I believe you'll want to use GWT deferred binding, using class replacement to bind a different version of your InjectorService depending on the user-agent. This will ensure the mobile version only has the mobile implementations compiled in (and downloaded)

So you would have InjectorServiceDesktop, InjectorServiceMobile, which both extend from InjectorService, then GWT.create(InjectorService.class), and let deferred binding decide which implementation it should use.

http://code.google.com/webtoolkit/doc/latest/DevGuideCodingBasicsDeferred.html#replacement

One instance of Ginjector with all versions seems bad as it means all code for both versions is always downloaded (and you certainly don't want to download all your desktop views into your mobile app)

EDIT: As Thomas points out in the comments, since the Injectors are generated classes, you'll need to put each InjectorServiceXXX inside a simple holder class that GWT.create()'s the InjectorServiceXXX, and use replacement to switch between the holders.

like image 120
aidanok Avatar answered Nov 03 '22 00:11

aidanok


To do what you want is actually rather complicated because your common injector interface, which is annotated with your Gin module, can not be pointing to an abstract Gin module. The Gin module pointed to by your Ginjector interface must be a concrete one. A concrete module cannot satisfy multiple configurations at the same time.

So what you do is: (a) Create your Ginjector interface, say ClientGinjector and your Module, ClientModule, for a desktop application.

(b) Create a second Ginjector interface, say ClientGinjectorTablet, extending the one you created in (a) but with an GinModule annotation pointing to a differnt Module, say ClientModuletablet.

-- Now you have two Ginjecor interfaces a default one and a secondary one for tablets, each one pointing to a module with its own Configure() implementations.

(c) Now you want to create Factory to get your Right Ginjector implementation. You can do this because the Ginjector you careated in (a) and (b) have a common demonitador which is the default interface created in (a). So you create an abstract facotry with a method such as this: public abstract ClientGinjector getInjector(); You create two children concrete classes One to get the Desktop/Default Ginjector and another one to get the Tablet Ginjector.

(d) Now you configure your module's gwt.xml just like Google IO on youtube explains you should do get your desired facotry during runtime, using GWT deferred bindings for each of your Ginjector factory.

(e) On your entrypoint the frist thing you is not to get a Ginjector but your factory for Ginjectors using GWT deferred binding. You call the abstract method that returns a ClientGinjector, your set.

(f) The epic fail at the end. Guice will not let you bind two times the same key (class plus annotation), even if you would be using different injectors (one for desktop and one for tablet). It seems that the key binding definitons are global, as soon as you have two modules redefining the same keys, that's the end of the adventure.

like image 43
99Sono Avatar answered Nov 03 '22 01:11

99Sono