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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With