1

I have a GWT MVP application using Activities and Places. This is inspired by Mauro Bertapelle's sample (in this thread), apparently based on some of Thomas Broyer's work.

Here's the problem: I have LoginActivity make an RPC call, which for a successful login, returns a User. This user has a role (e.g., admin, regular user, guest). Several Views and Activities, including a NavigatorView, depend on this role for what they show or do. How do I get this User instance to the other Activities?

I do not have a ClientFactory; injection (Gin) is used for instantiating the Views in the ActivityProviders which provide my Activities/Presenters, and the ActivityProviders are injected into my ActivityMapper. So this may reduce to a Gin question: how do I get the user reference where it's needed? This seems to be similar to this SO question about global references in MVP.

Consider me a Gin newbie, this is my first attempt at using it. I'm guessing there is a "Gin way" to make this happen, but I don't know Gin well enough to know the best way to do this (if Gin should be used at all).

Much thanks.

Edit 1: Despite my best efforts searching SO for a similar question, I just found this question which is pretty much identical to mine (is the SO algorithm for finding "Related" links better than the search?). I'm thinking that the Gin answer by David is on the right track.

I don't think that an EventBus solution is possible. I'm following the Google guidelines which involve instantiating Activity at every Place change, so a single Event by itself will not suffice.

Community
  • 1
  • 1
Glenn
  • 6,455
  • 4
  • 33
  • 42

2 Answers2

3

Something that I'm using on the server-side with Guice, and would work just as well on the client-side, is to bind to a custom Provider. In your case though, you'd have to make the provider a singleton and push the value into it from your RPC callback (rather than pulling it from some context). You'd first need a specific provider:

@Singleton
public class CurrentUserProvider implements Provider<User> {
  private User currentUser;

  public User get() { return currentUser; }
  public void setCurrentValue(User currentUser) {
    this.currentUser = currentUser;
  }
}

You'd bind User to the provider: bind(User.class).toProvider(CurrentUserProvider.class) In your RPC callback you'd inject a CurrentUserProvider so you can setCurrentValue but everywhere else you'd inject Provider<User> to keep CurrentUserProvider as an implementation detail. For very short-lived objects, you could directly inject a User value rather than a Provider<User>.

If you need to notify objects of the value change, you could dispatch an event on the global event bus.

Alternately, you could always use the concrete CurrentUserProvider type (which wouldn't have to implement Provider anymore) and possibly make it a HasValueChangeHandlers so you could register listeners on it rather than on the event bus (but you'd have to clean-up after yourself in your activities' onStop and onCancel to avoid memory leaks, whereas it's taken care of automatically if you register handlers on the event bus in onStart).

(if you ask me, I'd rather go away with authenticating from within the app whenever possible)

Thomas Broyer
  • 64,353
  • 7
  • 91
  • 164
  • Oh, it looks a lot like the answer to http://stackoverflow.com/questions/4629777/how-to-inject-a-runtime-dependency-like-a-logged-in-user-which-is-not-available that you already linked to! – Thomas Broyer Apr 30 '11 at 11:10
  • Thanks for these insights into Gin (and more thanks for your helpful blog articles). Truth be told, I'm skeptical about whether Gin is worth the trouble---perhaps as I understand it better I will be convinced. By the way, it seems that somehow my question led people to believe that I am doing client side _authentication_. I am not. I am doing client side _configuration_. – Glenn May 02 '11 at 15:58
  • 1
    Why not use a [dynamic host page](http://code.google.com/webtoolkit/articles/dynamic_host_page.html) then? it would replace your RPC call and make it possible to inject the value right away, as it's available by the time you create the Ginjector and inject your objects. – Thomas Broyer May 02 '11 at 16:04
  • Well, I never heard of Dynamic Host Page until you mentioned it. Thanks for the tip. – Glenn May 02 '11 at 23:39
1

I had similar requirements on a recent project.

When I get a reply from login (or logout) RPC I send a custom AuthenticationEvent on EventBus. All activities that are interested in this listen for this event. AuthenticationEvent has a reference to AppUser object which is null if user just logged out. AppUser contains all necessary data (privileges, groups, etc..) so that activities can inspect it and act upon it.

About global references: you can have a class with static methods providing data that you need. This class internally holds singleton references to needed instances. In my example I have static method AppUtils.getCurrentUser(). Internally it holds a reference to AppUser and also listens to AuthenticationEvent to set/reset this field.

As a side note: don't rely on client side to enforce access restrictions - you should separate your RPC servlets into two groups: public and private. Public can be accessed by anybody (this is basically login/logout RPC and some other public info RPC), while private RPC requires user to be authenticated. Access restrictions can be set per path/servlet: http://code.google.com/appengine/docs/java/config/webxml.html#Security_and_Authentication

Update:

  1. As you noted, class with static methods is not advisable in this setup, because it is not replaceable and this prevents testing (which is the whole point of using GIN).

  2. The solution is to inject a utility class holding globals (AppUtils) into activities that need the globals. AppUtils should be declared singleton in GIN configuration as one instance is enough for the whole app.

  3. To use Provider or not is just a question if you want to delay the initialization of dependencies (AppUtil is dependency). Since AppUtils is a singleton for the whole app it makes no sense to have it lazy initialized.

  4. Sometimes you will have a situation where you have multiple Activities shown on screen (in my case it was MenuBar and InfoBar). In this case, when user logs in you will need a way to notify them of the change. Use EventBus.

Peter Knego
  • 79,991
  • 11
  • 123
  • 154
  • Hi Peter, thanks for this input. Your point about enforcing restrictions client side is well taken; I'm aware of the issue and am just trying to display the appropriate GUI. Please see Edit 1 for a comment on an EventBus solution. The static solution for the global reference problem occurred to me, but some folks think it is bad form and it seems not "in the spirit" of the rest of the app. I have an AppController class where everything starts that would be a likely place to store the User for static access, and it may come to that. – Glenn Apr 29 '11 at 21:29
  • You can have one class, for example AppUtils, that holds all globals and is injected as a singleton. – Peter Knego Apr 29 '11 at 21:50
  • Regarding event bus: OP in the linked question is asking how to notify ALL activities about login/logout. This is not necessary. You should only notify activities that are shown at that time. When activity is started is hooks into event bus, and leaves it when it is hidden (this is done automatically in GWT 2.1 MVP with Activities) – Peter Knego Apr 29 '11 at 21:54
  • `Provider` is essentially used for "deferred injection". Instead of instantiating and injecting all dependencies when object is created, the Provider instantiates dependencies when they are needed (i.e. when provider.get() is invoked). This is also called lazy initialization. – Peter Knego Apr 29 '11 at 21:59
  • So if I understand correctly, your initial suggestion is to create, say, a singleton AppUtil class to hold stuff for global use, and inject it where needed. My LoginActivity would have it to set the authenticated User, and my other Activities would have it to access the user. – Glenn Apr 30 '11 at 03:54
  • So if I understand correctly, your initial suggestion is to create, say, a singleton AppUtil class to hold stuff for global use, and inject it where needed. My LoginActivity would have it to set the authenticated User, and my other Activities would have it to access the user. Am I getting this? Are you suggesting that another way to achieve this might be to use Provider to get the User via lazy initialization? When an Activity needed the User, it could get it via a Provider? At this point both solutions seem viable to me. – Glenn Apr 30 '11 at 04:00