Question about new activities on place changes

322 views
Skip to first unread message

Jens

unread,
Jun 15, 2011, 1:45:19 PM6/15/11
to google-we...@googlegroups.com
Hi,

I just integrating activities and got stuck when it comes to list selections. Hopefully someone with more practically experience can help me.

The application has one activity mapper that holds providers (GIN) of activity proxies (code splitting) and creates new, clean activity instances in its getActivity method. After creation it sets the current place. So pretty standard stuff I think.

Now I have for example an EmployeePlace that has two variables: 1.) the employer id, 2.) the employee id. The view for that place contains a list and a content area for the selected employee's details. So my EmployeeActivity uses the employer id to fetch & fill the list of employees and the employee id for an optional pre-selection.

My problem is that when I select something in the employee list I do a placeController.goTo(new EmployeePlace(currentEmployerId, selectedEmployeeId)) so that the history token reflects the current selection. But as I always create new activities the whole mechanism starts again and for each selection in my list the activity fetches all employees, fills the list and pre-selects the employee. So it doesn't feel right (loading overlay for the list on selection) and creates a lot of network communication overhead.

So is there a way to update the URL history token and NOT changing the place so that the activity not gets restarted?

I have tried to implement hashcode() and equals() in the EmployeePlace in a way that two places are equal once the employer ids are equal. That way the activity does not get recreated and started if the employee id changes but the URL does not reflect the list selection and placeController.getWhere() returns a place with no employee id (basically the "old" place prior selection)

Is there a way to solve this? I think I clearly missing something. Seems like a standard use case and currently I have no real idea how it can work without singleton activities.  

Thanks J.


Juan Pablo Gardella

unread,
Jun 15, 2011, 8:39:47 PM6/15/11
to google-we...@googlegroups.com
I think you can use a singleton in client side to manage getEmployesOf(employer). You can cache the last query.

2011/6/15 Jens <jens.ne...@gmail.com>

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-web-toolkit/-/l0nnHzqQrhcJ.
To post to this group, send email to google-we...@googlegroups.com.
To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.

Jens

unread,
Jun 16, 2011, 4:24:11 AM6/16/11
to google-we...@googlegroups.com
In that case I do not wanna cache things on client side. The app should be smart enough that it doesn't try to reload the list each time a list item is selected.

Thomas Broyer

unread,
Jun 16, 2011, 5:19:48 AM6/16/11
to google-we...@googlegroups.com
Instead of changing the equality behavior of your place, you should instead put your "list activity mapper" behind a FilteredActivityMapper and CachingActivityMapper: the FilteredActivityMapper will convert an EmployeePlace(empyer, employee) into, say, an EmployeePlace(employer, null), and the CachingActivityMapper will then return the same activity instance as previously (because the places are now comparable).

FYI, this is the approach used by the Expenses sample.

Jens

unread,
Jun 16, 2011, 5:47:51 AM6/16/11
to google-we...@googlegroups.com
Thanks I will try that. Haven't thought of a FilteredActivityMapper. I already tried a CachingActivityMapper on its own and because of the equality stuff it doesn't work.

I have just went through some examples but the layoutmvp example posted in this group also has an activity that starts over and over again on list selections (but data is local. If it was remote, everytime a request would be done) and the Expenses sample you have mentioned just has one activity mapper that holds one instance of its list and detail activities and calls updateForPlace(...) on them. So no new, clean activities..its more a singleton approach. Also there is no FilteredActivityMapper/CachingActivityMapper that wraps the ExpensesActivityMapper.

Maybe you mean a different example? In mobilewebapp its not used either.

Thomas Broyer

unread,
Jun 16, 2011, 6:51:39 AM6/16/11
to google-we...@googlegroups.com
It must have been in an earlier version. It's still in the bikeshed's Scaffold app (thus probably in any app generated by Spring Roo btw)

Jens

unread,
Jun 16, 2011, 11:56:07 AM6/16/11
to google-we...@googlegroups.com
Hm I have tried it now and at least the activity does not get restarted. But now if I bookmark my selected employee and access it later it can not be reselected because the activity filter always sets the employee id to null. The filter can not distinguish if the app is running and the user just selects/switches between employees or if the app has just been started via a bookmark and the employee id should not be nullified. So I have to make the filter a lot smarter. Just using placeController.getWhere() does not work as it already returns the new place so I can not make any decisions based on the old place. Of course I could set a flag in the place and let the activity control if the place should be filtered or not by setting the flag to true, but I think such a flag is not really an information that should go into a place as it does not really belong to the current app state. 

But I think its the only solution, isn't it? 

I am really confused why I have these "complications". Implementing places was really nice but now with "one shot activities" it somehow gets more difficult like I would expect. Currently it somehow feels easier to have singleton activities and then deal with activity updates when a new place has been set. Well but I favor a non singleton approach as I do not like to store singleton instances that I do not need.

How do you handle these place changes where the place actually keeps the same but the state changes? Do you really make the filter smarter so it can decide if it should nullify states to make places comparable or do you just live with it and create new activities and reload the data (possible via a cache, something simple like a static variable in the activity) each time they start? 

Am I somehow in a special situation because I do not have separate xyzListPlace/xyzDetailPlace classes along with two display areas/activity mappers and xyzListActivity/xyzDetailActivity like any other example I have seen so far? On a first thought I assume it wouldn't make a difference...?!

-- J.


Thomas Broyer

unread,
Jun 16, 2011, 12:08:44 PM6/16/11
to google-we...@googlegroups.com
Oh, so you have a single activity handling both the "master" and "details"? In that case, then I'd probably implement the "caching" "by hand" in the ActivityMapper (or in your own "proxy" activity mapper, inspired by CachingActivityMapper but doing the comparison using a more "sofisticated" approach than with a simple .equals()).
We did just that for a couple of places in our app.

Jens

unread,
Jun 16, 2011, 12:30:11 PM6/16/11
to google-we...@googlegroups.com
Yeah its a migration to activities and for simplicity I have only defined a single display area. Thats somehow the "work area" of the app. 

The reason is that I have a custom widget (layout panel) that can do quite a lot of things. So for each place I have that custom widget that effectively wraps a list selection, toolbar and the "real" content area and that widget goes into the display area associated with my activity manager. So my Activity has to handle both: list selection and loading the selected item.

I have thought about removing the list out of that widget and instead create a separate display area/activity for it.. but that would result in to much refactoring for an initial migration to activities as that custom widget is used everywhere in the app. Well and I haven't had a clue that not doing so would lead to the "problems" I now have :) If I would have two separate display areas, only the one displaying the list selection would need that place filtering/caching, right?

I'll take a look at a custom CachingActivityMapper.. we'll see.

--J.

Jens

unread,
Jun 17, 2011, 10:37:51 AM6/17/11
to google-we...@googlegroups.com
Ok a Custom "CachingActivityMapper" was easy to implement and works but there is still a case I am not happy with. If a user bookmarks EmployeePlace(1,123) and 123 gets deleted the activity would redirect to EmployeePlace(1, null) to keep the URL in sync (activity can not preselect the deleted item). But now the user could hit the browsers back button and is back on the URL for EmployeePlace(1,123). But as my activity now does not get notified (its cached and no start or setPlace is called) it can not redirect again to EmployeePlace(1, null). Thus nothing would be selected but the URL would imply that something should be selected.

So as I can not recreate the activity (the list would reload) the only way to solve this problem would be to call setPlace on the cached activity each time the place changes and let the activity react. But this is the same thing I would do if my activities were singletons. The only difference is that now I have only one variable storing the last activity (vs. all activities being singletons which would need a bit more memory).

Thomas Broyer

unread,
Jun 17, 2011, 11:58:47 AM6/17/11
to google-we...@googlegroups.com
Because you activity lasts longer than "a place", it should listen to PlaceChangeEvent (as if it were a singleton, except that it can be garbage collected and will be recreated if you go to another activity in the mean time), or the ActivityMapper should update it with the new place.

But actually, I wouldn't do a "redirect" if I were you. I'd rather either do as if there was no id in the place (despite being one in the URL) or display an error message that the request employee (or whatever) doesn't exist.

tanteanni

unread,
Jun 24, 2011, 8:06:02 AM6/24/11
to google-we...@googlegroups.com
Thomas/Jens.

"... and the CachingActivityMapper will then return the same activity instance as previously ..."

how to achieve that? i am able to create caching/filtered mapper but Filter returns a place and all my mappers are returning "new activities"? How to get / hold the old activity without saving it in a global field (singleton approach?!)? so my problem is not to filter the place - redirect to another place but to get the old activity returned.

Juan Pablo Gardella

unread,
Jun 24, 2011, 8:12:24 AM6/24/11
to google-we...@googlegroups.com
Are you use gin?

2011/6/24 tanteanni <tant...@hotmail.com>
Thomas/Jens.

"... and the CachingActivityMapper will then return the same activity instance as previously ..."

how to achieve that? i am able to create caching/filtered mapper but Filter returns a place and all my mappers are returning "new activities"? How to get / hold the old activity without saving it in a global field (singleton approach?!)? so my problem is not to filter the place - redirect to another place but to get the old activity returned.

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.

tanteanni

unread,
Jun 24, 2011, 8:21:57 AM6/24/11
to google-we...@googlegroups.com
no

Thomas Broyer

unread,
Jun 24, 2011, 8:34:30 AM6/24/11
to google-we...@googlegroups.com
The common pattern is ActivityManager → FilteredActivityMapper → CachingActivityMapper → MyActivityMapper.

The FilteredActivityMapper's Filter transforms places to "common cases" that can be compared with .equals(). In a Spring Roo app (at least of the kind that came with the first version of the Roo/GWT integration), the filter for the "master" region transformed EntityPlace(SomeProxy.class, "someId") into EntityListPlace(SomeProxy.class) for instance. So whichever the "someId" of two consecutive EntityPlace(SomeProxy.class, …) the filtered places would compare equal. It could have transformed them into EntityPlace with the same proxy type and, san, an empty ID, but transforming to the EntityListPlace makes the "MyActivityMapper" easier to write, as it only has to handle EntityListPlace.

The CachingActivityMapper compares two consecutive places with .equals() to decide what to do. If they're equal, it returns the previously returned activity (a reference of which it keeps in a field). Otherwise, it asks the "MyActivityMapper" to provide an activity for the place (generally a new instance), and it stores the place it was passed as argument, and the activity it returns, into fields; for the next round.

tanteanni

unread,
Jun 24, 2011, 9:08:08 AM6/24/11
to google-we...@googlegroups.com
i didn't understand much thomas - sorry.

in meantime i wrote a little sandbox app to boil my problem down to a minimum amount of code. My Problem is either i didn't understand anything or "CachingActivityMapper" is a misleading term because where is the cache and the cached activity. All i got so far is a class ("CachingMenuMapper") with a "FilteredActivityMapper.Filter". But its not filtering any activities it only maps places. And every place is mapped via some "getActivity(place)" to get "new activity".

So i understood filter/mapping places via "FilteredActivityMapper.Filter" but i don't know how to write a "MyActivityMapper" (the end in your chain) that is able to return an old activity. i am sure i am miss something in between but at the moment my old activity (menuList) is gone at the moment i go to contentPlace (and reading here i think there must be a way without saving activity in global field).

Thomas Broyer

unread,
Jun 24, 2011, 9:17:28 AM6/24/11
to google-we...@googlegroups.com
FilteredActivityMapper and CachingActivityMapper are the one from gwt-user.jar, you only have to write a FilteredActivityMapper.Filter to "transform" places, and an ActivityMapper which will provide the activities (the one I called MyActivityMapper in my previous message).

Mauro replied a few minutes ago in earlier threads, with some sample code.

tanteanni

unread,
Jun 24, 2011, 9:52:08 AM6/24/11
to google-we...@googlegroups.com
ok thats clear enough but there is my problem: providing an activity mapper that returns a cached activity. how does such an getActivity -method looks like (my only idea is to return an activity saved in some global field). but i can't believe that "CachingActivityMapper" could provide a cached activity?!
so no matter in what place the filtering ends, it ends always in an ActivityMapper's getActivity-Method that returns a new Activity. My question is how/where to get the cached activity? Will always the getActivity be called and i have to manage to get a cached one or is this "wrapped" by MyCachingActivity's filter? in latter case, how to avoid the call getActivity of wrapped Mapper? The Filter returns a place, i tried to always return the same place "...return place;..." - but it doesn't matter, allways a new activity is created.

(in meantime i got Mauro's new example to work - but understanding it is now working will be difficult. - at least for me)

Thomas Broyer

unread,
Jun 24, 2011, 10:19:03 AM6/24/11
to google-we...@googlegroups.com


On Friday, June 24, 2011 3:52:08 PM UTC+2, tanteanni wrote:
ok thats clear enough but there is my problem: providing an activity mapper that returns a cached activity. how does such an getActivity -method looks like (my only idea is to return an activity saved in some global field). but i can't believe that "CachingActivityMapper" could provide a cached activity?!

tanteanni

unread,
Jun 24, 2011, 10:44:52 AM6/24/11
to google-we...@googlegroups.com
thx thomas for your never ending endurance, i guess i came a little closer after reading the source.
Sorry for that: But what is the value of lastPlace and lastActivity after construction time?

And i miss the connection with Mauro's current code: If His CachingMApper returns "new MailListPlace()" the cached activity is used. But if the same place is returned a new activity is created. But reading the source says some places have to be equal to get "lastActivity". So what place should be equals to what place on calling the method Place filter(Place place), to get a cached activity?

Mauro Bertapelle

unread,
Jun 24, 2011, 11:07:42 AM6/24/11
to Google Web Toolkit


On Jun 24, 4:44 pm, tanteanni <tantea...@hotmail.com> wrote:
> thx thomas for your never ending endurance, i guess i came a little closer
> after reading the source.
> Sorry for that: But what is the value of lastPlace and lastActivity after
> construction time?
>

both null, of course..

> And i miss the connection with Mauro's current code: If His CachingMApper
> returns "new MailListPlace()" the cached activity is used. But if the same
> place is returned a new activity is created. But reading the source says
> some places have to be equal to get "lastActivity". So what place should be
> equals to what place on calling the method Place filter(Place place), to get
> a cached activity?

new Places are instantiated every time a placeChangeEvent occurs. So
when comparing new place with cached place, even if the Places are of
the same type (say MailListPlace) the instances are not, and that's
why we need to override Place.equals to compare class type and not
instances to let CachingActivityMapper works.

Jens

unread,
Jun 24, 2011, 11:52:28 AM6/24/11
to google-we...@googlegroups.com
Its pretty easy. If you have looked at the source code of CachingActivityMapper you see that it only calls place.equals(lastPlace) to check if it has to return the previous activity or has to create a new one. After construction of CachingActivityMapper both lastPlace and lastActivity are null and thus the first place.equals(lastPlace) will always return false and a new Activity for that requested place is created. Lets say the first place change has been done via placeController.goTo(new EntityPlace(1)) and now a second place change occur via placeController.goTo(new EntityPlace(2)).

The CachingActivityMapper will not return a cached activity if you do not have overwritten .equals() and .hashcode() in the EntityPlace class. If you do not overwrite both methods, the ones from Object will be used and .equals() in Object does a instance check (return this == other). But as you create new instances of EntityPlace the equals call in CachingActivityMapper will always be false.
So if you overwrite equals/hashcode you would normally check if the class types are the same and if all variables (= internal state) are the same. If both is true then both places are equal. But even with such an equals method in EntityPalce the CachingActivityMapper still does not work because now new EntityPlace(1).equals(new EntityPlace(2)) is still false as they do not have the same internal state.

Thats why you use a filter. In your Place filter(Place place) method you would do something like:

Place filter(Place place) {
  if(place instanceof EntityPlace) {
    return new EntityPlace(null);
  }
}

So you "nullify" that state of the place and now the CachingActivityMapper will always receive an EntityPlace without any state information and thus equals will return true and thus the cached activity for that place type will be reused. So thats the basic idea on how to make CachingActivityMapper work. By the way the PlaceController will of course always have the correct Place with the correct internal state if you call placeController.getWhere().

In my case I've done it in a slightly other way. So I have taken my MainActivityMapper of my application and in its getActivity method I do:

Activity getActivity(Place place) {
  if(place != null && this.lastPlace != null &&  place.getClass() == this.lastPlace.getClass()) {
    return this.lastActivity;
  }

  this.lastPlace = place;

  if(place instanceof XYZPlace) {
    return new XYZActivity(place);
  } else if(....) {.....} .....
}

That way I will always get a cached activity if two or more places in a row have the same type. So basically I integrated the caching into my normal MainActivityMapper and I do not need an extra Filter and do not need GWT's CachingActivityMapper. 
As every activity is now cached it has to implement PlaceChangeEvent.Handler to get notified on each place change. That way the activity can update its state based on the place although the activity is cached. In the way I did it I wouldn't need to implement .equals/hashcode in my places but I have done it because placeController will not fire a place change event if two places are equal (maybe you somehow accidentally called placeController.goTo(new EntityPlace(1)) twice in a row).

But keep in mind that I do it that way because I have only one ActivityMapper and one Activity for each Place. It was the easiest solution for me at the time being because I migrate the app to Activities and my activities deal with both list selection and loading of the selected object. But maybe my example shows you that its always a bit application dependent how to implement things. Once you got the basic idea how you can cache activities you can make it fit into your application.

-- J.

tanteanni

unread,
Jun 27, 2011, 2:32:07 AM6/27/11
to google-we...@googlegroups.com
thx to all 3 of you! my very little example is now working (is there a place to upload examples? it is nice to understand activities and places without MVP)

"placeController.getWhere()" is the essence that made it clear for me. thats the place the comparison uses with the place given to goTo, right?

But Jens how you implemented this.lastActivity in your Mapper? Probably i will change my code again. As i mentioned in some other thread, i had code looking like yours (one mapper and one main activity with control over many presenters).  You stated "Ok a Custom "CachingActivityMapper" was easy to implement and works but there is still a case I am not happy with..." With that you mean your special inside-mapper-caching? Please tell how this works (probably with saving activity in some global field?)
the loop way via filtered and cached mappers used for such simple use cases (just let a presenter/acivity be) still don't feels good.

Jens

unread,
Jun 27, 2011, 11:45:34 AM6/27/11
to google-we...@googlegroups.com
Oh yeah small typo in my MainActivityMapper example. It has to be:

 if(place instanceof XYZPlace) {
    this.lastActivity = new XYZActivity(place);
    return this.lastActivity;
  } else if(....) {.....} .....

So its just implemented as a field that stores the activity. Nothing fancy ;-)

-- J.

tanteanni

unread,
Jun 28, 2011, 1:47:22 AM6/28/11
to google-we...@googlegroups.com
thx that's realy nice - on first sight much better than filtering/caching/overriding equal just to get the same as before.
Reply all
Reply to author
Forward
0 new messages