Inner Activity Place Token Changes

113 views
Skip to first unread message

Mike H

unread,
Jan 20, 2012, 2:17:34 PM1/20/12
to Google Web Toolkit
I am working on an application built around Activities and Places. I'm
trying to do something that seems more difficult than it should be,
which makes me doubt I am using Activities and Places in the correct
manner.

The application contains 2 activities - one to search for a list of
customers and another to view details of a selected customer.
Selecting a customer changes Activity via the PlaceController, this
all seems fine, history navigation works as expected. The problem
comes when adding a filter to the search activity. The requirement is
that as filters are selected, they perform client-side filtering of
the search results and are also bookmark-able and part of the history.
So when the user selects a filter, I don't want to start and stop the
Activity (as this removes then adds the view, and the customer list is
also retrieved from the server on start), but I do want to update the
browser history so the user can press "back" to return to their
previous filter and also bookmark selected filters. I've tried a few
things:

1. Make the activity a Singleton so the Activity Manager will not
start and stop the activity (as the default equals would return true,
I suppose equals could also be implemented). Store a member variable
in the Activity that can be used to indicate whether the activity has
started or stopped. When the user selects a filter, create a Place
Token for the current activity with the required filter values, then
call PlaceController.goTo. When the ActivityMapper sets the filter
values from the Place Token on the Activity, check if the activity is
already started and then apply the filter if so.

2. Again, make the Activity a Singleton and perform the
PlaceController.goTo method when applying a filter as above. Introduce
a new sub-interface of Activity, RestartableActivity, that has an
onRestart method. Amend (or copy in to a new class since the required
members are private) the Activity Manager to call the onRestart method
if the current activity equals the existing activity, and apply the
filter in the onRestart method.

3. Have the Activity delegate to a singleton Presenter. Have the
presenter maintain the initial search results (so they are not
refreshed on the Activity start), and issue the PlaceController.goTo
method above to apply filters. Rather than relying on a Activity start
to refresh the list, explicitly refresh the list either periodically
or on specific user actions.

None of these seem like good solutions - in fact, they all seem like
pretty bad solutions that are overly complicated for something that
feels like it should should be simple to do. Personally, I liked the
idea behind attempt 2 - a restart type method on the Activity - but
this was such a hack to implement it just felt wrong.

So my questions are:

1. Is this an appropriate use of Activities and Places?
2. Has anyone else implemented something similar? If so, how?

Thanks
Mike

Thomas Broyer

unread,
Jan 22, 2012, 10:06:53 AM1/22/12
to google-we...@googlegroups.com
We've done something similar to your "attempt #1" except that instead of "setting the place" from the ActivityMapper, the activity simply listens to the PlaceChangeEvent. Works very well, and I must say it *does* feel like a "good solution" to me.

Mike H

unread,
Jan 22, 2012, 11:38:22 AM1/22/12
to Google Web Toolkit
Thanks for taking the time to read my post and reply Thomas. So, just
to make sure I understand, have you implemented the Activity as a
singleton to prevent the Activity Manager reloading the same activity?
If so, do you still need to know in the Activity if it is currently in
a "started" state?

Just to expand on why I think these are bad solutions - I think I
consider my attempts bad solutions because they all seem to be trying
to get round a limitation of the Activity Manager - even with your
solution, is it right that the Activity Manager is bypassed and the
Activity itself is coupled to the PlaceChangeEvent? I thought the idea
of the Place Controller and Activity Manager was to avoid the
activities having to be concerned about handling these events. Also, I
assume your Activity must have to look inside the PlaceChangeEvent to
check if it is for the Place associated with the Activity? If so, this
is really the job of the ActivityMapper, and having the Activity know
what Place it is duplicates that information in two places.


Patrick Tucker

unread,
Jan 22, 2012, 12:39:34 PM1/22/12
to Google Web Toolkit
The 2011 Google I/O session "High-performance GWT: best practices for
writing smaller, faster apps" talks a little about this. If you
haven't watched the video yet, it might be worth your time.

Mike H

unread,
Jan 22, 2012, 1:02:27 PM1/22/12
to Google Web Toolkit
Thanks Patrick, I'll have a watch of that.

In the meantime, I think the below change request would solve the
issue quite nicely:

http://code.google.com/p/google-web-toolkit/issues/detail?id=7140

Thomas Broyer

unread,
Jan 22, 2012, 5:06:26 PM1/22/12
to google-we...@googlegroups.com


On Sunday, January 22, 2012 5:38:22 PM UTC+1, Mike H wrote:
Thanks for taking the time to read my post and reply Thomas. So, just
to make sure I understand, have you implemented the Activity as a
singleton to prevent the Activity Manager reloading the same activity?

Not a singleton, but we're using some kind of "caching" in the ActivityMapper, something really similar to a CachingActivityMapper (but we baked it into our mapper). Something like:
if (shouldReturnSearchActivity) {
   if (lastSearchActivity == null) {
      lastSearchActivity = createSearchActivity();
   }
   return lastSearchActivity;
} else {
   // don't keep the activity forever, let it be garbage collected
   lastSearchActivity = null;
   // handle all other cases
}

If so, do you still need to know in the Activity if it is currently in
a  "started" state?

All our activities share a common ancestor that already tracks this for us, and we check "isActive() && event.getNewPlace() instanceof OurSearchPlace" in the PlaceChangeEvent.Handler, but I'm not sure it's needed (the instanceof would be enough to tell whether we're still on the activity or already navigating away).

In retrospect, I think passing the info from the ActivityMapper (as you suggested) is "cleaner", as our approach tightly couples the activity and the places (which is the responsibility of the ActivityMapper).

Just to expand on why I think these are bad solutions - I think I
consider my attempts bad solutions because they all seem to be trying
to get round a limitation of the Activity Manager - even with your
solution, is it right that the Activity Manager is bypassed and the
Activity itself is coupled to the PlaceChangeEvent? I thought the idea 
of the Place Controller and Activity Manager was to avoid the
activities having to be concerned about handling these events. Also, I
assume your Activity must have to look inside the PlaceChangeEvent to
check if it is for the Place associated with the Activity? If so, this
is really the job of the ActivityMapper, and having the Activity know
what Place it is duplicates that information in two places.

Yes. In retrospect, I think passing the info from the ActivityMapper (as you suggested) is "cleaner", as it avoids the coupling: the activity simply exposes an API advertising that the filter can be changed midway between the activity's start() and onStop(), and the ActivityMapper takes advantage of it.

Thomas Broyer

unread,
Jan 22, 2012, 5:15:13 PM1/22/12
to google-we...@googlegroups.com
I don't understand how your activity gets the info about the Place (in your case, the filter to apply).

If we think about the ActivityMapper as the one place that knows about both the Place and the Activity and how to bind them together, then each Activity should expose its own API and the ActivityMapper should then call it. In your case, that could be:

// in the activity
public void setFilter(String filter) { ... }

// in the mapper
if (place instanceof CustomersListActivity) {
   if (lastPlace instanceof CustomersListActivity) {
      lastActivity = createSearchActivity(((CustomersListActivity) place).getFilter());
   } else {
      ((CustomersListActivity) lastActivity).setFilter(((CustomersListActivity) place).getFilter());
   }
} else {
   // handle other places
}
// keep track of the last seen place
lastPlace = place;
return lastActivity;

Mike H

unread,
Jan 23, 2012, 3:24:03 AM1/23/12
to Google Web Toolkit
In our application, the Mapper passes the info from the Place to a
setter exposed by the Activity, just as you have done below. In my
attempt 1, the code to re-apply the filter is in the setFilter method
- it checks if the Activity has started, and then applies the passed
filter. If it not, it is stored as a member and applied at onStart.
Reply all
Reply to author
Forward
0 new messages