2. The <P extends Place> type parameter should be better documented.
As I understand it from reading the code, this is intended so you can
use an application-specific Place subclass as the base class for all
your Places and it then saves you the cast from Place to
BaseMyAppPlace (or ScaffoldPlace in the scaffold app). Specifically,
it is not intended, as you could think at first sight, to "only see" a
subset of places you're interested in (say you have an activity that
only maps places extending BaseRecordPlace, you shouldn't use an
ActvityManager<BaseRecordPlace> as there's no guarantee you'll only be
passed BaseRecordPlace instances (this is partly because of how Java
generics are implemented, so any experienced Java developper should
get it right). Removing the generics wouldn't hurt that much and would
prevent confusions in what the type parameter means.
3. As implemented now, an Activity can call the Display back several
times. I've found a use case for that but I'd like to know if it's
intended or not (and whichever the outcome, it should probably be
documented). My use case: we have a "dashboard like" interface, with
regions similar to "gadgets". For better UX (performance experience),
we'd like to first display the view with "loading..." placeholders and
asynchronously load the data to show in each region. I thought about
implementing each "gadget" as an Activity, managed by the parent
Activity directly rather than an ActivityManager; i.e. the
DashboardActivity, in its start(), starts each sub-activity, each with
a Display corresponding to a region on the screen, and displays itself
right away. To handle loading errors, the idea is that each sub-
activity displays an "error view" with a "retry" link; when the user
clicks "retry", the request is retried and if successful the Display's
showActivityWidget is called again, this time with the "real" view.
Would this be a "supported use case"? If not, given that it's not
using an ActivityManager, I could always make it work, but then it
would mean the subactivities know that they are subactivities with a
slightly different Display contract than other activities (which means
we'd probably use other interfaces than Activity and Display to avoid
confusions).
4. plumbing with History could only be a matter of calling
PlaceController.goTo on History ValueChange, and calling
History.newItem(token, false) on each PlaceChangeEvent; with some
PlaceMapper or HistoryMapper to map history tokens to places (in my
implementation, I called it HistoryMapper, I also made it a dependency
of the PlaceManager but now think it should just be another component
listener to those events on the event bus). But I'd like things to
happen a bit differently for an even better user experience: similarly
to how I think the current activity should only be stopped when the
new one is fully started, I think the history token should only change
at the time too. This is tricky however because there can be more than
one ActivityManager in an app (or should there be a single one?).
Right now, the workflow is:
1. currentActivity.mayStop()
2. currentActivity.onStop(), nextActivity.start(),
currentActivity=nextActivity; the currentActivity's view is still
displayed at that point, but it probably no longer has a Delegate
because onStop() is likely to have called setDelegate(null)
3. ... (time passes, waiting for the server to answer the new
activity's request; the previous activity's view is still displayed,
so the user still thinks it can interact with it, cancelling the
previous request)
4. response comes back from server, the new activity's view is finally displayed
What I was describing is:
1. currentActivity.mayStop()
2. nextActvity.start(); nextActivity knows it can be cancelled until
it calls showActivityWidget signaling it's "fully loaded"
3. ... (time passes, waiting for the server to answer the
nextActivity's request; the currentActivity's view is still displayed,
*and* responsive, as the activity hasn't yet been stopped at that
point)
4. response comes back from server, nextActivity calls
showActivityWidget => currentActivity.onStop(),
currentActivity=nextActivity
Now that I've written it down, I think there should be an onStopping()
call on the currentActivity at the time nextActivity is start()ed, on
step 2, so that on step 3 the currentActivity can cancel() the
starting nextActivity without necessarily going to another place.
This is needed because returning null from mayStop() doesn't
necessarily mean onStop will be called (another Activity, in another
ActivityManager, might have returned a non-null value), and returning
non-null doesn't necessarily mean onStop won't be called, because it's
just a mean to ask confirmation to the user. Note that because mayStop
can be called from window.onclosing, onStop might not be called at
all; even if we automatically call onStop in window.onclose, onStop
would have to be very "lightweight": no XHR/RPC/RequestFactory.
In the end, the workfow I'm asking for is:
1. currentActivity.mayStop()
2. currentActivity.onStopping(), nextActvity.start(); nextActivity
knows it can be cancelled until it calls showActivityWidget signaling
it's "fully loaded", and currentActivity knows it will be stopped, and
is passed a "handle" so it can cancel it.
3. ... (time passes, waiting for the server to answer the
nextActivity's request; the currentActivity's view is still displayed,
*and* responsive, as the activity hasn't yet been stopped at that
point; currentActivity can cancel nextActivity's loading, and the
whole navigation altogether)
4. response comes back from server, nextActivity calls
showActivityWidget => currentActivity.onStop(),
currentActivity=nextActivity
I understand it's a major change, which can be hard to coordinate
between multiple ActivityManager. Maybe it should map to following
events in the eventbus:
- PlaceChangeRequest => mayStop
- PlaceChangeAknowledge => onStopping/start
- PlaceChangeCancel => onCancel (should it also tell the "onStopping
activities" that one of them cancelled the move?)
- PlaceChange => only dispatched from showActivityWidget.
This seems closely related to the plumbing with History.
I would understand if you find this too complex and don't implement it
(or even try to); and I wouldn't mind, as you probably have more
experience (or can talk to others at Google with such experience) than
me and know if it's worth it or not.
If it's not though, I'd love to know how you handle errors and network
latency (i.e. latency between start() and showActivityWidget(), where
the view of the previous activity is still displayed –unless you think
about replacing it with a "loading screen", I noticed the TODO in the
code– but the activity already stopped), or if you code as if there
would be no error and network (and server) would be fast (or at least
"fast enough"), as was suggested in the "Architecting for performance
with GWT" talk at I/O.
No. I just thought about using the AbstractRecordListActivity for one
of these "gadgets", so started to think about using Activities; but I
would have done something else eventually if
AbstractRecordListActivity hadn't existed.
Note that I'm nesting activities but not using an ActivityManager, so
it's just a matter of using an existing API and trying not to overload
its intended behavior.
OK, we'll think about it (our approach looked a bit complex when we
started implementing it yesterday), thanks for the feedback.
--
Thomas Broyer
/tɔ.ma.bʁwa.je/