How to use injection via GIN with UiBinder and Widgets?
Asked Answered
H

2

8

I'm using GWT 2.4 with gwt-platform 0.7 and gin 1.5.0.

I've built a library for dynamic (live) translation of my GWT application. So every widget will get notified when the LocaleChangeEvent gets fired and then ask my TranslationDictionary to get the new String to display.

The widgets actually look like this:

public class LocaleAwareLabel extends Label implements LocaleChangeEventHandler {
    TranslationDictionary dictionary;
    String translationToken;

    public LocaleAwareLabel(TranslationDictionary dictionary, EventBus eventBus, String translationToken) {
        this.dictionary = dictionary;
        this.translationToken = translationToken;
        eventBus.addHandler(LocaleChangeEvent.TYPE, this);
        getCurrentTranslationFromDictionary();
    }

    public void getCurrentTranslationFromDictionary() {
        this.setText(dictionary.getTranslation(translationToken));
    }

    @Override
    public void onLocaleChange(LocaleChangeEvent event) {
        getCurrentTranslationFromDictionary();
    }
}

As you can see: I can't easily use this widget with UiBinder, at the moment I inject EventBus and TranslationDictionary in my View and use @UiField(provided=true)like this:

@UiField(provided=true)
LocaleAwareLabel myLabel;

@Inject
public MyView(TranslationDictionary dictionary, EventBus eventBus) {
    widget = uiBinder.createAndBindUi(this);

    myLabel = new LocaleAwareLabel(dictionary, eventBus, "someTranslationToken");
}

What I'd like to have: Using my widgets without @UiField(provided=true), so I can simply put them inside a ui.xml like this:

<custom:LocaleAwareLabel ui:field="myLabel" translationToken="someTranslationToken" />

I know I can set the translationToken via UiBinder using:

public void setTranslationToken(String translationToken) {
    this.translationToken = translationToken;
}

But then I still have the problem that I can't use a zero-args constructor because of EventBus and TranslationDictionary. And additionaly I can't call the getCurrentTranslationFromDictionary() inside the constructor, because the value of translationToken of course gets set after the constructor.

Would be nice if someone can provide a solution, maybe with code examples.

And P.S. I'm a total injection-noob, but from my understanding gin may somehow solve my problem. But I don't know how.

Thank you!

Handbag answered 11/7, 2012 at 13:59 Comment(0)
A
5

This is not possible currently, because UiBinder won't ever call into GIN to instantiate widgets. For that you have to use @UiFactory methods, or provide already-built instances with @Uifield(provided=true).

There's a request for enhancement already to better integrate the two. You can track it here: http://code.google.com/p/google-web-toolkit/issues/detail?id=6151

As an alternative, you can requestStaticInjection to inject a Provider into a static field, and call the provider's get() from within your constructor:

public class LocaleAwareLabel extends Label {
   @Inject Provider<EventBus> eventBusProvider;
   @Inject Provider<TranslationDictionary> dictionaryProvider;

   private final TranslationDictionary dictionary;
   private final EventBus eventBus;
   private final string translationToken;

   @UiConstructor
   public LocaleAwareLabel(String translationToken) {
       this(dictionaryProvider.get(), eventBusProvider.get(), translationToken);
   }

   // For cases where you can use injection, or inject params yourself
   @Inject
   public LocaleAwarelabel(TranslationDictionary dictionary, EventBus eventBus,
           String translationToken) {
       this.dictionary = dictionary;
       this.eventBus = eventBus;
       this.translationToken = translationToken;
   }

You might also be interested into GIN's support for Assisted-Inject, to make it easier to write @UiFactory methods or initialize @UiField(provided=true) fields.

Aq answered 11/7, 2012 at 15:1 Comment(4)
Thank you. @UiFactory looks pretty much the same as @UiField(provided=true). The @UiConstructor approach looks more like what I want, but still not perfect! In my head are two other possibilities (but I don't know how to implement them): 1. I read somewhere about GinUiBinder (from gwt-platform). 2. From what I've read I thought that gin could provide some kind of factory that automatically injects EventBus and TranslationDictionary and then just provides the LocaleAwareLabel(String translationToken) constructor. Or isn't that possible because of the @UiConstructor annotation?Handbag
Forgot to ask: Has static injection some kind of disadvantages? First thought: The method requestStaticInjection doesn't look like someone really should use it (just for cases of emergency). Second thought was: In Java world static is evil (for performance reasons etc.), but I can't imagine how it gets handled in GWT.Handbag
AFAIK, GinUiBinder was an experiment and no longer works with newer versions of GWT (I might be wrong though). The other point is AssistedInject, but it creates a factory, it doesn't do any magic with your classes. As for static, static state is evil, whichever the language or platform; in this case it's not really a problem, it just feels bad/odd.Aq
Thans for your reply. I think I will go with static injection. So I don't have to pollute my View with things that are only meant for the widgets. Or have I forgotten something?Handbag
P
6

There's currently a little bit of a concept mismatch between Dependency Injection and UiBinder, but the way I currently use it is:

private final Provider<LocaleAwareLabel> labelProvider;

@Inject
public MyView(TranslationDictionary dictionary, 
              EventBus eventBus, 
              Provider<LocaleAwareLabel> labelProvider) {

  this.dictionary = dictionary;
  this.labelProvider = labelProvider;
  initWidget(uiBinder.createAndBindUi(this));
}

@UiFactory
public LocaleAwareLabel buildLocaleAwareLabel() {
  return labelProvider.get();
}

Then I can create as many labels as I want in my ui.xml:

<g:HTMLPanel>
  <custom:LocaleAwareLabel translationToken="abc"/>
  <custom:LocaleAwareLabel translationToken="xyz"/>
</g:HTMLPanel>

In the LocaleAwareLabel, I use a setter method for the translation token, and override onLoad():

private String translationToken;

@Inject
public LocaleAwareLabel(TranslationDictionary dictionary, EventBus eventBus) {
  this.dictionary = dictionary;
  initWidget(uiBinder.createAndBindUi(this));
}

@Override
protected void onLoad() {
  super.onLoad();
  doSomethingWithTheTranslationToken(); // do this here, not in the constructor!
}

public void setTranslationToken(final String translationToken) {
  this.translationToken = translationToken;
}

Example for AssistedInject

MyView changes to:

private final LocaleAwareLabelFactory labelFactory;

@Inject
public MyView(TranslationDictionary dictionary, 
              EventBus eventBus, 
              LocaleAwareLabelFactory labelFactory) {

  this.dictionary = dictionary;
  this.labelFactory = labelFactory;

  initWidget(uiBinder.createAndBindUi(this));
}

@UiFactory
public LocaleAwareLabel buildLocaleAwareLabel(final String translationToken) {
  return labelFactory.create(translationToken);
}

LocaleAwareLabel:

public interface LocaleAwareLabelFactory {
  LocaleAwareLabel create(final String translationToken);
}

@Inject
public LocaleAwareLabel(TranslationDictionary dictionary, 
                        EventBus eventBus, 
                        @Assisted String translationToken) {

  this.dictionary = dictionary;
  initWidget(uiBinder.createAndBindUi(this));
  doSomethingWithTheTranslationToken(); // now you can do it in the constructor!
}

In your Gin module, add:

install(new GinFactoryModuleBuilder().build(LocaleAwareLabelFactory.class));

Make sure to add guice's assistedinject jar (e.g. guice-assistedinject-snapshot.jar) to your classpath.

Pellet answered 11/7, 2012 at 15:38 Comment(6)
Thank you. I'll try it later. I've already asked in the comment to Thomas Broyer's post: What about the GinUiBinder from gwt-platform? I haven't found any real documentation. The name 'sounds' like it could use Gin with UiBinder, but I don't really know...Handbag
@Chris: this is basically the approach I was suggesting with AssistedInject: replace the Provider with your factory, add parameters to the @UiFactory method (works like @UiConstructor) and pass them to the factory.Aq
Could you please provide an example for that @AssistedInject thingy. I can't really imagine what you mean.Handbag
@Benjamin: Ok, I added an example. BTW, there are a few drawbacks to static injection. The official Guice documentation says: "This API is not recommended for general use because it suffers many of the same problems as static factories: it's clumsy to test, it makes dependencies opaque, and it relies on global state." You may get into trouble, if multiple modules try to set a different provider on the static field...Pellet
@Thomas: Thanks for the AssistedInject idea!Pellet
Looks a little bit nicer than the previous example. But staticInjection is so much shorter. I think I can ignore most of the problems: These are really basic widgets that don't need testing, the TranslationDictionary itself can be tested without staticInjection. For this one dependency I think I can ignore the opaqueness. This global state thingy is the only thing I can't imagine in JavaScript. I'll write a small app and look at the pretty compiled code. One more question: What do you mean "set different providers on the static field"?Handbag
A
5

This is not possible currently, because UiBinder won't ever call into GIN to instantiate widgets. For that you have to use @UiFactory methods, or provide already-built instances with @Uifield(provided=true).

There's a request for enhancement already to better integrate the two. You can track it here: http://code.google.com/p/google-web-toolkit/issues/detail?id=6151

As an alternative, you can requestStaticInjection to inject a Provider into a static field, and call the provider's get() from within your constructor:

public class LocaleAwareLabel extends Label {
   @Inject Provider<EventBus> eventBusProvider;
   @Inject Provider<TranslationDictionary> dictionaryProvider;

   private final TranslationDictionary dictionary;
   private final EventBus eventBus;
   private final string translationToken;

   @UiConstructor
   public LocaleAwareLabel(String translationToken) {
       this(dictionaryProvider.get(), eventBusProvider.get(), translationToken);
   }

   // For cases where you can use injection, or inject params yourself
   @Inject
   public LocaleAwarelabel(TranslationDictionary dictionary, EventBus eventBus,
           String translationToken) {
       this.dictionary = dictionary;
       this.eventBus = eventBus;
       this.translationToken = translationToken;
   }

You might also be interested into GIN's support for Assisted-Inject, to make it easier to write @UiFactory methods or initialize @UiField(provided=true) fields.

Aq answered 11/7, 2012 at 15:1 Comment(4)
Thank you. @UiFactory looks pretty much the same as @UiField(provided=true). The @UiConstructor approach looks more like what I want, but still not perfect! In my head are two other possibilities (but I don't know how to implement them): 1. I read somewhere about GinUiBinder (from gwt-platform). 2. From what I've read I thought that gin could provide some kind of factory that automatically injects EventBus and TranslationDictionary and then just provides the LocaleAwareLabel(String translationToken) constructor. Or isn't that possible because of the @UiConstructor annotation?Handbag
Forgot to ask: Has static injection some kind of disadvantages? First thought: The method requestStaticInjection doesn't look like someone really should use it (just for cases of emergency). Second thought was: In Java world static is evil (for performance reasons etc.), but I can't imagine how it gets handled in GWT.Handbag
AFAIK, GinUiBinder was an experiment and no longer works with newer versions of GWT (I might be wrong though). The other point is AssistedInject, but it creates a factory, it doesn't do any magic with your classes. As for static, static state is evil, whichever the language or platform; in this case it's not really a problem, it just feels bad/odd.Aq
Thans for your reply. I think I will go with static injection. So I don't have to pollute my View with things that are only meant for the widgets. Or have I forgotten something?Handbag

© 2022 - 2024 — McMap. All rights reserved.