Monday, 9 April 2012

Mobile Apps on Spring

Once again, I could not resist the temptation to get some hands-on experience with another goody of Spring's. Spring Mobile has been around for a good while and much has been said about it already. Thus this post remains concise and focused on specific features. I took a closer look at device resolution and site preference management. To add some value to the existing examples I tried to make most of Spring 3.1 and as a result, there is not a single line of XML in my mini application. All the configuration is done via annotations.
If you are like me and prefer code samples to theory, go ahead and download the example project straight away. Here is all you need to know:

It is a web application consisting of a single page only. It comes in two flavours, standard and mobile. I made them purposely very different. The mobile version runs on jQuery Mobile and makes thus it's standard counterpart (plain HTML only) look really poor.

In sync with the usual approach, the page reveals some details about the device it is viewed in. A mobile device is detected automatically and the mobile page is displayed by default. You always have a chance to switch to the other view by using a link on the respective page. To have some fun you might consider downloading the User Agent Switcher.

Finally, there is no XML configuration and no web.xml either. I tested it on Tomcat 7, but could not get it work with Jetty 7. You might need to check your web server documentation before you deploy.

That's pretty much all you need to know about the application. The rest of the post explains the example in detail, step-by-step.

When it comes to the device resolution Spring provides only the absolute minimum. Actually, it only tells whether the device is a mobile:
package org.springframework.mobile.device;

public interface Device  {
    
    boolean isMobile();
} 
To cater for a detailed device analysis Spring has partnered with the Wurfl project. There are two major benefits of Wurfl. First, it is for free. Secondly, it integrates well with Spring (although that might not necessarily be the case in the near future). Apart from that I could find another tool called simply Handset Detection. Since it is a commercial product I did not consider it for my little exercise.

Since I like to keep my code vendor-independent I created a wrapper around Spring's and Wurfl's features and hid it behind my very own interface:
package org.zezutom.springmobile.model;

import javax.servlet.http.HttpServletRequest;

/**
 * Resolves a device, based on a client request.
 */
public interface DeviceResolver {

    DeviceInfo resolveDevice(HttpServletRequest request);
}
Not to interfere with a Device as understood by Spring (and Wurfl, both using the same name:-) I call my interpretation of a device a DeviceInfo. It is not too far-fetched, as I am interested in a only a subset of information about the device rather than the device itself. However, my primary concern is distinguishing between mobile devices and the rest of the world and therefore I let the class implement the Spring's Device interface:
package org.zezutom.springmobile.model;

import org.springframework.mobile.device.Device;

import java.util.Collections;
import java.util.Map;

/**
 * Provides a vendor-independent description of a device.
 */
public class DeviceInfo implements Device {

    private boolean mobile;

    private boolean displayNormal;

    private String id;

    private String userAgent;

    private String markUp;

    private Map capabilities;

    public DeviceInfo(boolean mobile) {
        this.mobile = mobile;
    }

    @Override
    public boolean isMobile() {
        return mobile;
    }

    // the usual getters and setters
}
Please note the boolean field called displayNormal. It has something to do with site preferences, which I am going to explain further down this post. Another field worth an explanation is the map of capabilities. This maps exactly to the Wurfl's amazing resolution capabilities. I use it to provide device details on the sample's front-end.

Let's move on to the actual device resolution. As I said my implementation is a mere wrapper around the underlying tools which, I hope, makes it really straightforward and easy to understand:
package org.zezutom.springmobile.model;

import net.sourceforge.wurfl.core.Device;
import net.sourceforge.wurfl.core.WURFLManager;
import org.springframework.mobile.device.DeviceUtils;
import org.springframework.mobile.device.site.SitePreference;
import org.springframework.mobile.device.site.SitePreferenceUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * Makes use of the WURFL tools to resolve the device.
 */
@Service
public class WURFLDeviceResolver implements DeviceResolver {

    @Resource
    private WURFLManager manager;

    @Override
    public DeviceInfo resolveDevice(HttpServletRequest request) {

        // Spring caters for the elementary information
        // (is this a mobile?)
        boolean mobile = DeviceUtils.getCurrentDevice(request)
                                                   .isMobile();

        // Look for site preferences
        SitePreference sitePreference = 
           SitePreferenceUtils.getCurrentSitePreference(request);

        // Decide the type of the device and how to display the device
        DeviceInfo deviceInfo = new DeviceInfo(mobile);
        deviceInfo
        .setDisplayNormal(SitePreference.NORMAL
                                        .equals(sitePreference));

        // Other properties are resolved using the WURFL capabilities
        Device device = manager.getDeviceForRequest(request);

        deviceInfo.setId(device.getId());
        deviceInfo.setCapabilities(device.getCapabilities());
        deviceInfo.setMarkUp(device.getMarkUp().toString());
        deviceInfo.setUserAgent(device.getUserAgent());

        return deviceInfo;
    }
}
The last missing bit from the sample's bare bones is the controller. It utilizes the device resolver to get to the device details. It makes them available on the view and invokes the page appropriate for the obtained device. As one would expect, it is rather tiny:
package org.zezutom.springmobile.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.zezutom.springmobile.model.DeviceInfo;
import org.zezutom.springmobile.model.DeviceResolver;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Controller
public class HomeController {

    @Resource
    private DeviceResolver deviceResolver;

    @RequestMapping("/index.htm")
    public String index(Model model, HttpServletRequest request) {
        DeviceInfo deviceInfo = updateModel(model, request);
        return getView(deviceInfo.isDisplayNormal());
    }

    private DeviceInfo updateModel(Model model, 
                    HttpServletRequest request) {
        DeviceInfo deviceInfo = deviceResolver.resolveDevice(request);
        model.addAttribute("device", deviceInfo);
        return deviceInfo;
    }

    private String getView(boolean normal) {
        return normal ? "index" : "index-mobile";
    }
}
To conclude on the device resolution, Spring tells you if a mobile is involved. Should you need more you are better off using a third-party tool, Wurfl probably being the most logical choice these days. I would like to make one last remark on this topic. You can actually make Spring pass the Device as an argument into your controller's method:
..
import org.springframework.mobile.device.Device;
..
@Controller
public class HomeController {

    @RequestMapping("/index.htm")
    public String index(Device device) {
        return device.isMobile() ? "mobile" : "normal"; 
    }
    ..
}
Cool as it is, I could not get it work without resorting to XML configuration and thus excluded it from my solution. In reality this is certainly not a problem and it would be probably favoured over anything else.

The site preference management comes in handy when a user has a choice to say what website flavour do they prefer. A home-grown solution could look like that:
..
import org.springframework.mobile.device.Device;
..
@Controller
public class HomeController {

    @RequestMapping("/index.htm")
    public String index(boolean displayNormal) {
        return displayNormal ? "normal" : "mobile"; 
    }
    ..
}
Nothing huge, but if you stick to naming conventions you can save yourself some coding and leave it up to Spring. Here is a snippet from a JSP page:
..
<a href="${currentUrl}?site_preference=mobile">Mobile</a><br />
<a href="${currentUrl}?site_preference=normal">Normal</a>
..
An extra configuration needs to be done to turn the feature on. Once enabled, you are free to inspect the incoming HTTP request to learn what site is preferred. Reckon my device resolver earlier in this post:
..
import org.springframework.mobile.device.site.SitePreference;
import org.springframework.mobile.device.site.SitePreferenceUtils;
..
// Look for site preferences
SitePreference sitePreference = 
  SitePreferenceUtils.getCurrentSitePreference(request);
..
For a maximum comfort, you can have the site preference passed as an argument into your controller's method. Again, yet another piece of configuration needs to be added to make it happen.

That pretty much concludes all I wanted to say. I rushed through two out of three major parts of the mobile framework, the third being the site switching. To me, Spring Mobile is not an extensive tool to learn. Even though it is not astonishingly rich in functionality, it works best when accompanied by a right stack of technologies. I see it as a good-to-have complement to the Spring MVC.

Download Source Code

12 comments:

  1. Hi Tomas,

    I have tried to run your sample app in tomcat 7 but its not running(If i verified in tomcat manager application is not started) could you please let us know which server we can run this sample application and procedure for running.

    ReplyDelete
    Replies
    1. Hello,
      my bad, I forgot to check documentation of the war plugin. By default, it fails if no web.xml is found. This is now fixed, enjoy.

      Delete
    2. Hi Tomas,

      I added the web.xml and try to run this application still its not running could you please let me know the source location of fixed one(with web.xml).

      Thanks,
      Sadhana

      Delete
    3. Hi Sadhana,
      instead of adding the web.xml, which has no real use here, I fixed the configuration of the war plugin. You can review the committed change at: https://github.com/zezutom/Spring-Mobile-Demo/commit/cf6e3d27323a099df12e28ed5a876b5e6cd40c12

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Many thanks for sharing your knowledge

    App Builder

    ReplyDelete
  4. I really like your blog very much and i hope you will continue this good work in the future as well.mobile apps

    ReplyDelete
    Replies
    1. Hi Keith, many thanks. I was busy in the last few months, but I will continue with this blog. Great to hear you like it, I will do my best:)

      Delete
  5. thanks for share...

    ReplyDelete
  6. about time, no thank you to the one week headstart though!




    Mobile Application

    ReplyDelete
  7. Thanks for the nice blog. It was very useful for me. Keep sharing such ideas in the future as well. This was actually what I was looking for, and I am glad to came here! Thanks for sharing the such information with us.
    cell phone detector

    ReplyDelete