Reputation: 1043
Each of my Activities needs a correspoding singleton View implementation. What's the best strategy to inject them into activities?
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...
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.
constructing the injector with GWT.create in Activity's ctor. Useless, as they would not longer be singletons.
Upvotes: 5
Views: 3336
Reputation: 696
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.
Upvotes: 3
Reputation: 985
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.
Upvotes: 2
Reputation: 64541
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.
Upvotes: 7