Question

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.

Was it helpful?

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.

OTHER TIPS

I chose a slightly different method that has all the flexibility you need. I don't remember where I picked this design pattern up, but it wasn't my idea. I create the activity as such

public class MyActivity extends AbstractActivity{

    private MyView view;
    @Inject static PlaceController pc;


    @Inject
    public MyActivity(MyView view) {
        super();
        this.view = view;
    }

    public MyActivity withPlace(MyPlace myPlace) {
        return this;
    }
...
}

Then I use this in the activity mapper like this:

public class MyMapper implements ActivityMapper {

    @Inject Provider<MyActivity> myActivityProvider;

    public Activity getActivity(Place place) {

        if ( place instanceof MyPlace){
            return myActivityProvider.get().withPlace(place);
        } else if
...

Also make sure the View is declared singleton in the gin module file.

In my experience a good practice is to have separate activity mappers to deal with the places and activities (the mapping). In the activity you have the presenter, here is example of a activity:

public class ActivityOne extends AbstractActivity {

  @Inject
  private Presenter presenter;

  @Override
  public void start(AcceptsOneWidget panel, EventBus eventBus) {
    presenter.go(panel);
  }

}

The presenter have the view injected inside, it is constructed(the presenter) when "go" method is called. The presenter is declared as singleton in the GIN module and views are usually singletons(with some exceptions like small widgets that appear in many places).

The idea is to move the contact with view inside the presenter (as the goal of the presenter is to deal with the logic and retrieve/update data to/from the view, according to MVP). Inside the presenter you will have also the RPC services, you do not have to declare them because GIN will "magically" make instance for you, by calling GWT.create Here is an example of a simple presenter:

    public class PresenterOneImpl implements Presenter {

      @Inject
      private MyView view;


      @Inject
      private SomeRpcServiceAsync someRpc;


      @Override
      public void go(AcceptsOneWidget panel) {
        view.setPresenter(this);
        panel.setWidget(view);
        updateTheViewWithData();
      }
}

At the end I must note that there are some activities, like the one for the menu, which deal with places and the view directly in order to display the current state. These activities are cached inside the mapper to avoid new instance every time the place is changed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top