gwtdependency-injectiongwt-gingwt-activities

using GIN in GWT Activities


Each of my Activities needs a correspoding singleton View implementation. What's the best strategy to inject them into activities?

  1. constructor injection Activity constructor is called from an ActivityMapper's getActivity(). The ctor already has a parameter (a Place object). I would have to create the ActivityMapper with all possible views injected. Not good...

  2. method injection - "A function so annotated is automatically executed after the constructor has been executed." (GWT in Action, 2nd Ed.) Well, "after the ctor has been executed" is apparently not fast enough because the view (or an RPC service injected this way) is still not initialized when the Activity's start() method is called and I get a NPE.

  3. constructing the injector with GWT.create in Activity's ctor. Useless, as they would not longer be singletons.


Solution

  • What worked best for us was to use Assisted Inject.

    Depending on the case, we defined activity factories either in the activity itself, in a package (for building all the activities in that package), or in the ActivityMapper.

    public class MyActivity extends AbstractActivity {
       private final MyView view;
    
       @Inject
       MyActivity(MyView view, @Assisted MyPlace place) {
          this.view = view;
          ...
       }
       ...
    }
    
    public class MyActivityMapper implements ActivityMapper {
       public interface Factory {
         MyActivity my(MyPlace place);
    
         FooActivity foo(FooPlace place);
    
         ...
       }
    
       // using field injection here, feel free to replace by constructor injection
       @Inject
       private Factory factory;
    
       @Overrides
       public Activity getActivity(Place place) {
          if (place instance MyPlace) {
             return factory.my((MyPlace) place);
          } else if (place instance FooPlace) {
             return factory.foo((FooPlace) place);
          }
          ...
       }
    }
    
    // in the GinModule:
    install(new GinFactoryModuleBuilder().build(MyActivityMapper.Factory.class));
    

    BTW, for method injection to work, you still have to create your activities through GIN, so you'd have the same issues as with constructor injection. There's no magic, GIN won't magically inject classes that it doesn't know about and doesn't even know when they've been instantiated. You can trigger method injection explicitly by adding methods to your Ginjector, but I wouldn't recommend it (your code would depend on the Ginjector, which is something you should avoid if you can):

    interface MyGinjector extends Ginjector {
       // This will construct a Foo instance and inject its constructors, fields and methods
       Foo foo();
    
       // This will inject methods and (non-final) fields of an existing Bar instance
       void whatever(Bar bar);
    }
    
    ...
    
    Bar bar = new Bar("some", "arguments");
    myGinjector.whatever(bar);
    ...
    

    A last word: I wouldn't pass the place object directly to the activity. Try to decouple places and activities, that allows you to move things around (e.g. build a mobile or tablet version, where you switch between master and detail views, instead of displaying them side by side) just by changing your "shell" layout and your activity mappers. To really decouple them, you have to build some kind of navigator though, that'll abstract your placeController.goTo() calls, so that your activities never ever deal with places.