Question

I understand that Places & Activities are separate from MVP. P&A are for managing browser history, and MVP is an architectural pattern. But, obviously, they interleave and overlap in areas, and this question is about how they can work cohesively together, in unity.

I just read up on GWT Places & Activites, and recommended MVP structure, and my head is spinning. I need someone to confirm I have the basic idea down.

A PlaceHistoryHandler has both a PlaceController and PlaceHistoryMapper. When you enter a specific URL in a browser address bar, the PlaceHistoryHandler uses its PlaceHistoryMapper to determine which Place it should pass to its PlaceController. The PlaceController, in turn, fires an appropriate PlaceChangeEvent onto the EventBus.

One or more ActivityManagers are listening on the bus for such PlaceChangeEvents, and map those Places to an Activity, which is supposed to be the Presenter component in an MVP architecture.

The returned, concrete Activity (presenter) should have a model-injected view (typically UiBinder) that implements AcceptsOneWidget. This AcceptsOneWidget component is then started (via Activity#start(...)) and GWT - automagically - presents its contents to the browser.

Is anything I've said incorrect, misleading or misunderstood? If so, please correct me. A lot of code is about to be written based on this understanding...

Was it helpful?

Solution

Maybe this can help you a little bit

I used this schema when I was getting confused :

enter image description here

OTHER TIPS

It sounds like you have a fairly decent grasp of the concepts. I've used the Activities/Places API a few times, and I still find it kind of confusing. Here is another overview on how you can think of the components:

PlaceController - What you use to tell the ActivityManager where to transition to next, using goTo.

ActivityManager - Manages running activities. Calls start,stop,show, ect.

ActivityMapper - Think of this as a factory. It knows what Activity to create based on a given place. This is where I usually inject my RPC service.

Place - Think of this as an "address" to a particular view in your application. The PlaceTokenizer is usually specified in here, but that is more of a convenience.

PlaceHistoryMapper - This is the class that will take the url token, and using the PlaceTokenizers you specified, create a Place out of it.

Activity - The activity code should be able to take a Place object, and get your app to that place. If two Place objects are the same, they should show the same thing each time.

Here is a (probably not stellar) example of a test app I wrote that uses Activities Places. I have two parts of the app that use this: https://github.com/aglassman/jgoo/tree/master/JGoo/src/com/jgoo/client/appnav https://github.com/aglassman/jgoo/tree/master/JGoo/src/com/jgoo/client/crud/nav

Activities Places are set up here: https://github.com/aglassman/jgoo/blob/master/JGoo/src/com/jgoo/client/CrudLauncher.java

Here is the test app in action, you can see the different ways I used PlaceTokenizers to get to different views. (Note, the datastore sometimes takes a few seconds to init, so if you "Get All", it can take awhile to load (no loading spinner, but it's working). If you click on the result text, it will bring you to the view of the object.

http://jgoo-sample.appspot.com/

Hope this helps!

UPDATE: Added Activity example, how it ties into MVP

In my example below, the PlaceTokenizer supplies an activity type, and if an edit is requested, a UUID is supplied to map to the particular contact. I use the Activity as a high level presenter, pretty much just to supply the lower level presenter with the initial data in info it needs to do its job. Within the lower level presenter, in this case RequestEditWidget, and ContactInfoWidget, I use UIBinder to create a view. Note that I don't currently have a way for the activity to use the mayStop / onStop methods, but that would just be a matter some extra code to interface with my widgets.

Each of these (edit, subscribe, request_edit) could have all been in their own activity, but I wanted them all to have the same place prefix.

package contactmanager.client.nav;

import contactmanager.client.ContactManagerServiceAsync;
import contactmanager.client.callback.BasicCallback;
import contactmanager.client.contact.info.ContactInfoWidget;
import contactmanager.client.contact.info.RequestEditWidget;
import contactmanager.shared.bundle.InitDataBundle;
import com.google.gwt.activity.shared.AbstractActivity;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.user.client.ui.AcceptsOneWidget;

public class ContactActivity extends AbstractActivity{

    public enum Activity {
        request_edit,
        edit,
        subscribe
    }

    private ContactManagerServiceAsync cmsa;
    private ContactPlace place;
    private PlaceController pc;

    public ContactActivity(PlaceController pc, ContactManagerServiceAsync cmsa,ContactPlace place)
    {
        this.pc = pc;
        this.cmsa = cmsa;
        this.place = place;
    }

    public void start(AcceptsOneWidget panel, EventBus eventBus) {
        switch(place.activity)
        {
            case request_edit:
                loadRequestEditPanel(panel);
                break;

            case edit:
                loadEditPanel(panel);
                break;

            case subscribe:
                loadSubscribePanel(panel);
                break;
        }
    }

    private void loadSubscribePanel(final AcceptsOneWidget panel) {
        cmsa.getInitDataBundle(new BasicCallback<InitDataBundle>() {
            @Override
            public void onSuccess(InitDataBundle result) {
                panel.setWidget(new ContactInfoWidget(pc,cmsa,result,null).getWidget());
            }   
        });
    }

    private void loadRequestEditPanel(final AcceptsOneWidget panel) {
        panel.setWidget(new RequestEditWidget(pc,cmsa).getWidget());
    }

    private void loadEditPanel(final AcceptsOneWidget panel) {
        cmsa.getInitDataBundle(new BasicCallback<InitDataBundle>() {

                    public void onSuccess(InitDataBundle result) {
                        panel.setWidget(new ContactInfoWidget(pc,cmsa,result,place.uuid).getWidget());
                    }
                });
    }

}

You have one thing wrong: you initialize the ActivityManagers with an AcceptsOneWidget and an ActivityMapper (and the global EventBus). The ActivityManager listens to PlaceChangeEvent and asks its ActivityMapper for the corresponding Activity, and start()s it, passing the AcceptsOneWidget it was initialized with, and a ResetableEventBus that wraps the global EventBus.
The AcceptsOneWidget is a slot on the page that'll receive the view for the activity. When the activity starts, it then passes its view (as an IsWidget; hint: Widget implements IsWidget itself) to the AcceptsOneWidget, and that's how it signals "this is what to display, and show it now". Note that it can do so synchronously (from within start()) or asynchronously (e.g. from the response to an RPC that start() triggered).

Regarding MVP, many people use the Activity as a presenter, but this is just one way to do MVP:

  • some use activities only as lifecycle managers for other components (e.g. widgets) that themselves use MVP (or not). An activity could thus create a presenter and view (or pick long-lived instances of either of them; again, generally the presenter is short-lived while the view –heavier to build– is a long-lived, e.g. a singleton) and initialize/reset them to the correct state; or it could just create/reuse a widget and initialize/reset it.
  • some use MVP inside widgets, as an implementation detail. CellTable (and actually almost all cell-based widgets) is one such example: it's complex enough that a presenter had a real value, but that presenter is not exposed at all in the API. You can mix this approach with the above: the activity creates/reuses a widget and initializes/resets it, and that widget uses MVP internally.

If you want to learn more about places and activities, I'd encourage you to read my blog posts about them:

This is of course in addition to the official documentation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top