Thursday, August 11, 2011

Custom Place Service

History management is probably one of the features with the most impact on user's experience and on your application architecture. In Mvp4g, it is also one of the most customizable features. This feature is organized around a Place Service and since 1.3.0, you can easily set your own. You can then override 100% of the default place service or you can just change part of it to manage history exactly the way you want.

In this tutorial, we're going to cover one of the most common cases to set a custom place service, changing characters used in the URL to separate the event name from the parameters. We will reuse the example from the previous post and add a custom place service. You can download the example here (Project: Mvp4g_Custom_Place_Service).

Creating a custom Place Service

To change the parameter that separates the event name from the parameters in the URL, all you have to do is create a class that extends PlaceService and override the getParamSeparator method.
public class CustomPlaceService extends PlaceService {

    protected String getParamSeparator() {
        return ":";
    }

}

Setting a custom Place Service

To set a custom place service, you need to annotate your event bus with @PlaceService and set the class of your place service.
@PlaceService( CustomPlaceService.class )
@Events( ... )
public interface CustomPlaceServiceEventBus extends EventBusWithLookup {
    ...
}
Mvp4g will then use this class to instantiate it.

You have now created and set a custom place service that will replace the default place service.

Particular case: “/”

You can use “/” to separate event name from the parameters but an extra step is required. This character is a particular case because it is also used to manage child modules history. To sum up, when you store an event of a child module in the history, the child module name followed by “/” is added to the event token. By default, you then have something like this:
childModuleName/eventName?parameters
So, if you set the “/” to separate event name from the parameters, your token looks like this:
childModuleName/eventName/parameters
If the event doesn't have any parameters, you will then have the following token:
childModuleName/eventWithNoParameterName
The issue here is this token won't be interpreted the way it is expected. Since there is only one “/” in this token, Mvp4g will assume it is used to separate parameters from the eventName so it will parse 'childModuleName' as the event name and 'eventWithNoParameterName' as the parameters.
To solve this issue, we need to make sure a “/” is always added to the token after the event name even if there is no parameter. Thus the previous token becomes:
childModuleName/eventWithNoParameterName/
To do this, we're going to modify our custom place service to change the default behavior. The PlaceService object uses a 'tokenize' method that generates a token from the event name and its parameters string representation. You can then override this method to automatically add “/” after the event name.
@Override
public String tokenize( String eventName, String param ) {
    //always add the paramSeparator since "/" is used 
    //for module separator and paramSeparator
    String token = eventName + getParamSeparator();
    if ( ( param != null ) && ( param.length() > 0 ) ) {
        token = token + param;
    }
    return token;
}
We now have created a custom place service where “/” is used as a separator.

What's next?

This tutorial is quite short and simple but it covers, I believe, one of the most important features to be aware of. Mvp4g provides a default mechanism to generate and retrieve the history token but it's entirely up to you to have the one you want. The part you have to override will depend on the changes you want to bring. If you want to modify the entire logic, you will probably have to override the 'convertToken' and 'place' methods. If you'd just like to alter the default generated token, you will most likely be looking at the 'tokenize' and 'parseToken' methods.

You can also create a custom place service not to modify Mvp4g logic but to add another action when the token changes. For example, you could easily add Google Analytics to your application:
public class CustomPlaceService extends PlaceService {
    ...    
    public String place( String eventName, String param, boolean onlyToken ) {
        //send info to Google Analytics
        sendToGoogleAnalytics(eventName);
        
        return super.place( eventName, param, onlyToken );
    }
}
This is probably a good example to develop in more details in a future post.

6 comments:

  1. "You can also create a custom place service not to modify Mvp4g logic but to add another action when the token changes. For example, you could easily add Google Analytics to your application:"

    This is about like what I had in mind, but how would I dispatch events to an EventBus from here?

    ReplyDelete
  2. Why do you want to fire an event inside the place method?

    ReplyDelete
  3. Well, I just knew you were going to ask me that. :-)

    Suppose I have a multi-module project, where child modules contribute a token to some NavigationPresenter running in the root module, without the child necessarily needing to know it's doing so. Think breadcrumbs.

    I'll admit that I might be barking up the wrong tree here, so if this is something that's easily accomplished another way, I'd appreciate hearing about it.

    I've considered using forwardToParent or broadcast interfaces, but I'd rather not have to remember to annotate every event with one of those two things. Seems like if I could just fire an event from place(), things would be much simpler.

    ReplyDelete
  4. Nevermind, I did find a better way. Thanks anyway, and also for the framework itself by the way.

    ReplyDelete
  5. I guess this is the easiest approach if you want to inform a presenter each time a place event is fired.

    One way you can access the event bus in your custom event place is to override the setModule method:
    public abstract class CustomPlaceService extends PlaceService {
    private MyRootEventBus eventBus;
    ...
    public void setModule( Mvp4gModule module ) {
    super.setModule( module );
    eventBus = (MyRootEventBus) module.getEventBus();

    Hope this help.

    ReplyDelete
  6. I updated the example with Mvp4g-1.4.0. One change in the tutorial: the place service doesn't need to be abstract anymore.

    ReplyDelete