59
7/13/2019 Documented Android Sample Application http://slidepdf.com/reader/full/documented-android-sample-application 1/59 3/24/2014 Documented Android Sample Application http://metagear.de/articles/android-sample/index.html metagear.de A – Sort-Of – Full-Fledged Android Sample Application – Walked-Through In-Depth August 12, 2012 · Robert Söding Show Table of Contents 1. Preface While the sales volumes of PCs and feature cell phones stagnate, smartphone – and, since recently, tablet PC – sales boom. Looking at operation system distributions in the mobile sector, iOS and Android are prevalent, squeezing other operation systems (like Symbian or Blackberry OS) out of the market (upcoming Windows Phone is still playing in its small niche). Although Apple has pioneered the mobile market with their iPhone and iPad, Android device sales are growing much more rapidly in both relative and absolute terms. For smartphones, in year 2012, Android has a 59% market share (iOS: 23%), and 145% growth per year (iOS: 89%). Still being a niche segment as of today, tablet PC sales are estimated to rapidly grow as well. In this segment it is forecasted that, while iOS will retain its leadership for several upcoming years, Android's relative growth is, and will be, higher. – See chapter Android Market Share for some resources on these issues. – The article at hand documents a sample application that puts the Android SDK (software development kit) to work. In contrast to just providing a Eureka! Hooray! , few-lines, blog posting that fakes its writer as an all-time problem solver, this sample application implements a - well, not so – real-world, complex, use case, which needs to be implemented at any rate – whether the Android APIs would fit – or wouldn't. – We've been trying t make this application reproducible as a whole, not just lightening single aspects of it. As this article does not  start at a "Hello World" entry level, interested readers are supposed to already have gone through introductory Android articles or tutorials (seriously). As for the server-side implementation, a basic knowledge of Servlet and Spring Framework technologies ought to be helpful, however that's not mandantory. Any feedback is appreciated and may be directed to [email protected].  If you don't want to install the sample application, straightly skip to the Sample Application section. 2. Prerequisites  Unfortunately, there are more than two or three steps involved in order to get the sample applicat ion running, so this cannot be considered a Hello World sample. Nevertheless, I've tested the deployment procedure, thus feel free to contact me in case of incidents – after "aunt Google" hasn't brought you forward. The application has been developed on Eclipse 3.6 (Helios) for Java EE Developers with the Android Development Tools (ADT) installed. The sample application makes use of Android's Location-Based Services (LBS) and Google Maps. Due to

Documented Android Sample Application

Embed Size (px)

DESCRIPTION

Android application sample

Citation preview

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 1/59

    metagear.de

    A Sort-Of Full-Fledged Android

    Sample Application Walked-Through

    In-Depth

    August 12, 2012 Robert Sding

    Show Table of Contents

    1. Preface

    While the sales volumes of PCs and feature cell phones stagnate, smartphone and, since recently, tablet PC

    sales boom.

    Looking at operation system distributions in the mobile sector, iOS and Android are prevalent, squeezing other

    operation systems (like Symbian or Blackberry OS) out of the market (upcoming Windows Phone is still playing in

    its small niche).

    Although Apple has pioneered the mobile market with their iPhone and iPad, Android device sales are growing

    much more rapidly in both relative and absolute terms. For smartphones, in year 2012, Android has a 59%

    market share (iOS: 23%), and 145% growth per year (iOS: 89%).

    Still being a niche segment as of today, tablet PC sales are estimated to rapidly grow as well. In this segment it

    is forecasted that, while iOS will retain its leadership for several upcoming years, Android's relative growth is,

    and will be, higher. See chapter Android Market Share for some resources on these issues.

    The article at hand documents a sample application that puts the Android SDK (software development kit) to

    work.

    In contrast to just providing a Eureka! Hooray!, few-lines, blog posting that fakes its writer as an all-time

    problem solver, this sample application implements a - well, not so real-world, complex, use case, which

    needs to be implemented at any rate whether the Android APIs would fit or wouldn't. We've been trying to

    make this application reproducible as a whole, not just lightening single aspects of it.

    As this article does not start at a "Hello World" entry level, interested readers are supposed to already have

    gone through introductory Android articles or tutorials (seriously). As for the server-side implementation, a basic

    knowledge of Servlet and Spring Framework technologies ought to be helpful, however that's not mandantory.

    Any feedback is appreciated and may be directed to [email protected].

    If you don't want to install the sample application, straightly skip to the Sample Application section.

    2. Prerequisites

    Unfortunately, there are more than two or three steps involved in order to get the sample application

    running, so this cannot be considered a Hello World sample.

    Nevertheless, I've tested the deployment procedure, thus feel free to contact me in case of incidents after

    "aunt Google" hasn't brought you forward.

    The application has been developed on

    Eclipse 3.6 (Helios) for Java EE Developers

    with the Android Development Tools (ADT) installed.

    The sample application makes use of Android's Location-Based Services (LBS) and Google Maps. Due to

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 2/59

    officially unresolved bugs related to using these with the Android Emulator in Android 2.2 and 2.3 (i.e., this one

    ), the sample application uses the

    Android SDK 2.1, API level 7, along with the corresponding

    Google APIs, API level 7.

    This SDK version can be installed from within the Android SDK and AVD Manager, which is included with the

    Android SDK :

    To run the server application,

    Apache Tomcat

    has been used (both 6.0 and 7.0 work).

    2.1. Downloading, Importing, and Running the Sample

    Application

    Download the zipped project sources, which comprise of the following Eclipse projects:

    mg-library-android a library of custom Android views, activities, and Android-specific utilities, which

    can be re-used in any Android project

    mg-pizzastore-android the Android client (our main application)

    mg-pizzastore-android-test automated tests for the Android client

    mg-pizzastore-server the server-side application

    mg-pizzastore-shared a library of domain / model classes, which are used by both mg-pizzastore-

    android and mg-pizzastore-server

    Make sure that you've set up the Android SDK in the Eclipse Preferences. As mentioned in the previous

    chapter, the SDK must contain the Android SDK 2.1, API level 7 version:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 3/59

    To import the aforementioned projects into Eclipse, click File --> Import --> Existing Projects into

    Workspace, and choose the mg-pizzastore-android.zip archive file previously downloaded.

    Assign a server to the mg-pizzastore-server project by completing the following steps:

    In Eclipse, open the Servers view by selecting from the main menu: Window --> Show View -->

    Servers.

    In the Servers view, right-click, and select New --> Server.

    Choose an existing Apache Tomcat 6.0 oder 7.0 installation, and in a subsequent dialog,

    assign the mg-pizzastore-server project.

    At this stage, there shouldn't be any compile errors in the projects, and we're heading towards running the

    sample application.

    Fix the mg-pizzastore-android project properties by right-clicking the project, Properties --> Android. In

    the field group Library, remove the referenced, but invalid mg-library-android project ...

    ... and newly add it:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 4/59

    Please excuse that annoyance. I've looked at the settings files but haven't found a better solution yet.

    To start the MapViewActivity, you need to obtain a custom Google Maps API Key . Next, edit the

    corresponding file mg-pizzastore-android/res/layout/map_linearlayout.xml accordingly. See chapter

    MapViewActivity: General Setup for additional information.

    It hasn't been hard to set up the Maps API Key, has it? ;-)

    To finally run the sample application from within Eclipse, first start the mg-pizzastore-server project by

    right-clicking it and selecting Run as --> Run on Server.

    Next, run mg-pizzastore-android by right-clicking its project root and choosing Run as --> Android

    Application (select a matching Android Virtual Device (AVD) in the Eclipse Run Configurations).

    To run the tests, right-click the mg-pizzastore-android-test project in Eclipse, and select Run As -->

    Android JUnit Test.

    2.2. Tip: Getting the android.jar Source Code

    Unfortunately, the Android SDK doesn't ship with the

    source code for android.jar, and you'll probably

    stumble upon that.

    Google'ing around, everyone recommends to checkout

    the source code from the Git source code management

    system at sources.android.com . However, with 2.6

    GB in download size and several installation steps

    involved, that may be overkill. Noone seems to realize

    that there's a lightweight alternative:

    Install the adt-addons Eclipse Plugin .

    Theoretically, this should be sufficient to view the

    Android class libraries' source code in Eclipse; however,

    it did not work for me. In this case find the matching

    sources.zip in

    $ECLIPSE_HOME/plugins/com.android.ide.eclipse.source*.

    These sources may be not perfectly accurate when they don't exactly match your Android SDK revision.

    3. The Sample Application

    The sample application represents an imaginary pizza store, where the user can select, and order, from a list

    of available pizzas. Additionally they need to fill in their address data. As a plus, the user can edit their location

    in a Google Map.

    At application start time, the list of pizzas is retrieved from the remote server, and at shopping cart checkout

    time, the order gets submitted to that server.

    3.1. Data Model

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 5/59

    3.1.1. Domain Classes

    The application's domain respectively, model classes are located at the mg-pizzastore-shared project.

    The following class diagram visualizes the entities and their relationships:

    The sample application's first, and

    main, screen is the Pizzas List,

    which lists a set of Pizzas along with

    their properties.

    When the user selects a Pizza into

    their Shopping Cart, that shopping

    cart displays a set of PizzaLineItems,

    where a PizzaLineItem while

    realizing all Pizza properties has an

    additional property quantity.

    In order to finally checkout their

    shopping cart, the user needs to fill

    in a User Data Form, which

    corresponds to a UserData instance.

    An Address contains user data that are required for Location-Based Services; such data include street, city,

    and a Country. UserData contains an Address plus some additional data such as the user's name and phone

    number.

    When the user finally checks out their shopping cart, an Order will be submitted to the server.

    For corresponding code see: mg-pizzastore-shared/de.metagear.pizzastore.model

    3.1.2. Using the JSON ObjectMapper with the Model Objects

    In the sample application, the Jackson ObjectMapper is used to marshal, resp., unmarshal, (our domain class)

    instances from, resp., to, JSON -encoded strings. We're using it when Dealing with Android's

    SharedPreferences as well as with the backend communications (HttpGetPizzasListTask and HttpPostTask)

    toward the server.

    The ObjectMapper requires some custom settings regarding the class' constructor and transient properties, as

    shown on our Address model object:

    @JsonIgnoreProperties({ "valid" })

    public class Address implements Serializable {

    // ...

    public Address(String street, String zipCode, String city, Country country) {

    street = street;

    zipCode = zipCode;

    city = city;

    country = country;

    }

    public Address() {

    }

    public String getStreet() {

    return street;

    }

    public void setStreet(String street) {

    street = street;

    }

    // ...

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 6/59

    @JsonIgnore

    public boolean isValid() {

    if (StringUtils.isEmpty(street) || StringUtils.isEmpty(zipCode)

    || StringUtils.isEmpty(city) || !country.isValid()) {

    return false;

    } else {

    return true;

    }

    }

    }

    For corresponding code see: mg-pizzastore-shared/de.metagear.pizzastore.model.Address

    First-off, the mapper requires either a nillary (default) constructor or (not used in the sample application) the

    @JsonCreator annotation on another, public, parametrized, constructor.

    Additionally, our boolean isValid() method is transient and evaluated on-the-fly, at runtime (accordingly,

    there is no matching setter). - Therefore, we're instructing the ObjectMapper to ignore the getter property

    (using the @JsonIgnore annotation) and not to expect a corresponding setter property (using the

    @JsonIgnoreProperties({ "valid" }) annotation).

    Consult the Jackson Annotations ApiDoc in case you'd need further information.

    3.2. Spring-Based Backend, and Remoting

    3.2.1. Overview

    As for the sample app's client/server connection, the HTTP transport protocol has been favored over other

    protocols (like using RMI, for instance) in order to avoid possible firewall restrictions. From there, JSON has

    been preferred over XML (or binary contents, like using Hessian ), and REST over XML Web Services,

    because of performance reasons and ease of implementation with Android.

    Spring Android provides a

    client-side API for JSON

    (and, alternatively, XML-)

    based REST operations and

    Android integration. At the

    server side, Spring Android is

    "simply" leveraging Spring's

    Web MVC Framework APIs.

    Just like other Spring

    Framework domains, Spring

    Android provides a set of templates, elegantly figuring out, and implementing, best practices.

    For related concepts, see chapter Remoting of my previously written article on the Spring Framework .

    You may also want to read chapter Spring MVC of the same article, although Spring MVC in portions has

    partially evolved from Spring 2.5 to 3.0.

    The sample application's MainController residing in project mg-pizzastore-server is basically implemented

    as follows:

    @Controller

    @RequestMapping("/*")

    public class MainController {

    @RequestMapping(

    value = "fetchpizzas",

    method = RequestMethod.GET,

    headers = "Accept=application/json")

    public @ResponseBody

    List fetchPizzasJson() {

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 7/59

    return getAllPizzas();

    }

    @RequestMapping(

    value = "postorder",

    method = RequestMethod.POST,

    headers = "Content-Type=application/json")

    public @ResponseBody

    String postOrderJson(@RequestBody Order order) {

    return "OK";

    }

    }

    For corresponding code see: mg-pizzastore-server/de.metagear.pizzastore.service.MainController

    We're configuring the server to listen to HTTP requests on the (exemplary) URLs http://localhost:8080/mg-

    pizzastore-server/fetchpizzas and http://localhost:8080/mg-pizzastore-server/postorder in case the

    application/json content type is supported, respectively, provided.

    From the Emulator's device point of view, the server address ain't localhost or 127.0.0.1 (that refer to its

    own loopback device) but 10.0.2.2.

    As for the @Controller, @RequestMapping, and @ResponseCode annotations, see the Spring MVC reference

    documentation. There is some configuration involved in WEB-INF/web.xml as well as in a couple of Spring

    bean configuration files under the folder WEB-INF. These settings are both documented in my previous Spring

    article and in the current Spring MVC reference documentation, both linked above.

    There is no database interaction involved. Instead, the List getAllPizzas() method simply returns a

    hard-coded list.

    3.2.2. JSON Encoding

    Somewhat "automagically", the server serves its response in a JSON-encoded format.

    In the above code snippet you can see that the server looks for the client-side HTTP headers

    Accept=application/json on HTTP GET requests, and Content-Type=application/json on HTTP POST requests.

    If both classes, org.codehaus.jackson.map.ObjectMapper and org.codehaus.jackson.JsonGenerator, are on the

    classpath, the Spring Framework will identify the ObjectMapper to perform object/JSON marshaling as Spring's

    MappingJacksonHttpMessageConverter has registered for the application/json MediaType and does employ that

    JSON ObjectMapper.

    We'll implement a custom MappingJacksonHttpMessageConverter later (in chapter Decoding the GZIP'ed

    Response), however you don't really need to know about which Spring or JSON classes are involved, exactly.

    (The Spring reference documentation itself doesn't even mention these internal matters.) Just make sure

    that jackson-core-asl-x.x.x.jar and jackson-mapper-asl-x.x.x.jar are on the classpath.

    The sample's application's remoting techniques have been derived, and inspired, by the spring-android-

    showcase project of the Spring Mobile Samples .

    While mouse-clicking Windows guys might hate SpringSource for forcing them into an extra command line tool

    (Git SCM ), the samples are to be checked out from the Source Code Management System (SCM) by issuing

    the following command:

    git clone git://git.springsource.org/spring-mobile/samples.git spring-mobile-samples

    You'll also want to check out the Spring Android source code:

    git clone --recursive git://git.springsource.org/spring-mobile/spring-android.git

    On Ubuntu, git can be installed by simply issueing sudo apt-get install git at the command line. On

    Windows, please consult the installation instructions at the Git Website .

    3.2.3. GZIP Compression

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 8/59

    Large HTTP downloads on mobile devices may be costly in measures of limited data tariffs, network speed,

    and battery life. Thus we're compressing the HTTP response body before sending it to the client, using GZIP-

    compression.

    To do so, we're implementing a custom servlet Filter:

    public class GzipFilter implements Filter {

    // ...

    @Override

    public void doFilter(ServletRequest request, ServletResponse response,

    FilterChain filterChain) throws IOException, ServletException {

    if ((request instanceof HttpServletRequest)) {

    HttpServletRequest httpRequest = (HttpServletRequest) request;

    HttpServletResponse httpResponse = (HttpServletResponse) response;

    String str = httpRequest.getHeader("accept-encoding");

    if ((str != null) && (str.indexOf("gzip") != -1)) {

    GzipResponseWrapper wrapper = new GzipResponseWrapper(

    httpResponse);

    filterChain.doFilter(request, wrapper);

    wrapper.finishResponse();

    } else {

    filterChain.doFilter(request, response);

    }

    }

    }

    // ...

    }

    For corresponding code see: mg-pizzastore-server/de.metagear.util.servlet.GzipFilter

    In WEB-INF/web.xml, this GzipFilter is configured to intercept responses of Spring's DispatcherServlet

    (which, itself, is configured to handle all requests):

    appServlet

    org.springframework.web.servlet.DispatcherServlet

    ...

    appServlet

    /

    gzipFilter

    de.metagear.util.servlet.GzipFilter

    gzipFilter

    appServlet

    For corresponding code see: mg-pizzastore-server/webapp/WEB-INF/web.xml

    The GzipFilter employs the custom GzipResponseWrapper, which decorates the HttpServletResponse to output a

    custom GzipResponseStream, which in turn uses the Java SE standard GZIPOutputStream library to encode

    HttpServletResponse's ServletOutputStream.

    See this article for a more in-depth discussion on this GZIP filter.

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 9/59

    Tomcat itself provides configuration options on GZIP compression, although these might not work with

    Tomcat's mod_jk . After all (and after the time of writing), using the PJL Compressing Filter might be

    preferred over what I'd chosen for this sample application. Looking at the source code, obviously, the PJL

    Filter has been written with expert knowledge and, observably, with loads of love and care.

    3.3. Android Client

    Hey, finally the fun part is soon to come!

    There are even screenshots to go! ;-)

    3.3.1. Retrieving the Pizzas List

    3.3.1.1. Overview

    See the following class diagram for an overview of the main classes and interfaces involved:

    Retrieving the Pizzas list, and ordering a Pizza, are two tasks of class PizzasListActivity, which is shown in

    the center of the above diagram. These two tasks are accomplished by the classes HttpGetPizzasTask and

    HttpPostTask (shown at the diagram's bottom), which both are components (has-a relation) of

    HttpGetPizzasTask. The latter implements two certain interfaces that the task classes expect in order to be

    able to call back certain interface methods when the tasks are completed, successfully.

    The upper part of the class diagram looks more complex than it is. Basically, class AsyncActivity (which can be

    re-used in Android Activitys as well as in ListActivitys) shows a progress dialog before the task starts and

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 10/59

    dismisses the dialog after it completes.

    In case a task fails, interface AsyncActivity defines method onAsyncTaskFailed(), which needs to be

    implemented by our main PizzasListActivity ( the diagram is inaccurate in not displaying the aforementioned

    method).

    When the Android client application initially loads, the main Activity, PizzasListActivity, initializes the

    download of an up-to-date List:

    Several techniques and frameworks are involved.

    The basic HTTP communication is accomplished by Spring's

    RestTemplate , which is used by the Spring Android project.

    The RestTemplate is configured to send a client-side HTTP GET

    header: Accept=application/json. As mentioned in chapter JSON

    Encoding, this header will be picked up at the server side as a

    command to deliver JSON-encoded contents. Just like at the server

    side, the Jackson JSON libraries need to be on the classpath to let

    Spring Android decode JSON automatically.

    From there, as the server-side response is GZIP-compressed, we're

    extending Spring Android's MappingJacksonHttpMessageConverter to

    decompress the server response. We're also extending Spring

    Android's RestTemplate to be aware of our custom GZIP-enabled

    HttpMessageConverter.

    Not yet finally, the download task needs to be decoupled from the

    main user interface (UI) thread into an independant thread that does

    not block the UI. The Spring Android samples provide exemplary

    classes to use the RestTemplate within an asynchronous Android

    AsyncTask , which does threading by leveraging the

    java.util.concurrent framework.

    And finally, when the download thread returns, it needs to update

    the state of the main UI thread. This is accomplished by using an

    Android Handler .

    As you can see, this is fairly complex. In the following chapters, each scope is discussed individually.

    3.3.1.2. HttpGetPizzasListTask

    The sample application's main Activity is PizzasListActivity, whose Activity.onCreate(Bundle) gets called

    when it is created the first time. That's from where (through an intermediate method) new

    HttpGetPizzasListTask(this, uri, 0).execute() gets called.

    HttpGetPizzasListTask a component of PizzasListActivity extends Android's AsyncTask (take a brief look at

    the API and APIDocs ), whose class structure the following screenshot shows:

    Its main method is execute(Params...), which - simplified calls the following methods, one after the other:

    onPreExecute()

    doInBackground(Params...) (which does its work in a separate, decoupled, thread)

    onPostExecute(Result)

    So, as mentioned in the chapter above, this task does not block the main UI thread. For details see below.

    We've already mentioned Spring Android , its RestTemplate for REST/HTTP-based communications, and the

    corresponding Spring Mobile Samples .

    The latter's DownloadStatesTask extends Android's AsyncTask and leverages Spring Android's RestTemplate in a

    separate thread.

    Basically, it is defined as follows, implementing the aforementioned three basic methods:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 11/59

    private class DownloadStatesTask extends AsyncTask {

    @Override

    protected void onPreExecute() {

    // before the network request begins, show a progress indicator

    showLoadingProgressDialog();

    }

    @Override

    protected List doInBackground(Void... params) {

    try {

    ...

    // Perform the HTTP GET request

    ResponseEntity responseEntity = restTemplate.exchange(

    url, HttpMethod.GET, requestEntity, State[].class);

    // convert the array to a list and return it

    return Arrays.asList(responseEntity.getBody());

    }

    catch(Exception e) {

    Log.e(TAG, e.getMessage(), e);

    }

    return null;

    }

    @Override

    protected void onPostExecute(List result) {

    // hide the progress indicator when the network request is complete

    dismissProgressDialog();

    // return the list of states

    refreshStates(result);

    }

    }

    }

    For corresponding code see: DownloadStatesTask

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 12/59

    3.3.1.3. Implementing a ProgressDialog

    In the above code snippet (which stems from an Spring Android sample), the

    sample DownloadStatesTask is an inner class of an Android Activity

    implementation, on which the task calls the methods showLoadingProgressDialog()

    and dismissProgressDialog().

    In contrast to that, our own HttpGetPizzasListTask is a self-contained class that

    calls these methods on an Activity that has been passed to its constructor (from where it is stored in an

    instance variable). This Activity therefore needs to implement our custom interface AsyncActivity, which is

    defined as follows:

    public interface AsyncActivity {

    void showLoadingProgressDialog();

    void dismissProgressDialog();

    // ...

    }

    For corresponding code see: mg-library-

    android/de.metagear.pizzastore.activity.async.AsyncActivity

    Doing so, our pizza-downloading PizzasListActivity extends AbstractAsyncListActivity, which is shown below:

    public abstract class AbstractAsyncListActivity extends ListActivity implements

    AsyncActivity {

    private Context context;

    private ProgressDialog progressDialog;

    public AbstractAsyncListActivity(Context context) {

    context = context;

    }

    @Override

    public void showLoadingProgressDialog() {

    progressDialog = ProgressDialog.show(context,

    context.getString(R.string.loadingProgressDialog_title),

    context.getString(R.string.loadingProgressDialog_text),

    true);

    }

    @Override

    public void dismissProgressDialog() {

    if (progressDialog != null) {

    progressDialog.dismiss();

    }

    }

    }

    For corresponding code see: mg-library-

    android/de.metagear.pizzastore.activity.async.AbstractAsyncListActivity

    3.3.1.4. HTTP GET, and JSON Decoding, and Using the RestTemplate

    In HttpGetPizzasListTask, the doInBackground(..) method is defined as follows:

    @Override

    protected List doInBackground(Void... params) {

    try {

    HttpHeaders requestHeaders = new HttpHeaders();

    List acceptableMediaTypes = new ArrayList();

    acceptableMediaTypes.add(MediaType.APPLICATION_JSON);

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 13/59

    requestHeaders.setAccept(acceptableMediaTypes);

    HttpEntity requestEntity = new HttpEntity(requestHeaders);

    RestTemplate restTemplate = new GzipJsonRestTemplate();

    ResponseEntity responseEntity = restTemplate.exchange(uri,

    HttpMethod.GET, requestEntity, Pizza[].class);

    return Arrays.asList(responseEntity.getBody());

    } catch (Exception e) {

    ...

    return null;

    }

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.task.HttpGetPizzasListTask

    Basically, we just add an application/json header and, next, call RestTemplate.exchange(..).

    Android's AsyncTask, from which our HttpGetPizzasListTask inherits, calls the doInBackground(..) method in a

    separate thread and, next, makes the results available in onPostExecute(..).

    Don't get confused because of the GzipJsonRestTemplate in the above code snippet - it's a custom subclass

    of the standard RestTemplate, which is covered in the following chapter.

    As already mentioned in chapter JSON Encoding (at the Server Side), the Jackson Core and Jackson Mapper

    JAR files need to be on the classpath. The RestTemplate's infrastructure will automatically detect them and

    use them to decode the JSON response.

    3.3.1.5. Decoding the GZIP'ed Response

    Spring's RestTemplate internally prepares a Lists. If the aforementioned Jackson JAR files

    are on the classpath, the RestTemplate also adds a MappingJacksonHttpMessageConverter to that list.

    These HttpMessageConverters register for certain Java classes and Spring MediaTypes (like application/json) via

    their methods canRead(..) and canWrite(..). If the RestTemplate finds a matching converter, that one will be

    selected to perform the unmarshaling, resp., marshaling.

    In the sample application, GzipEnabledMappingJacksonHttpMessageConverter extends

    MappingJacksonHttpMessageConverter to additionally decompress the InputStream that gets returned by the

    RestTemplate:

    public class GzipEnabledMappingJacksonHttpMessageConverter extends

    MappingJacksonHttpMessageConverter {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override

    protected Object readInternal(Class clazz, HttpInputMessage inputMessage)

    throws IOException, HttpMessageNotReadableException {

    if (isGzipEncoded(inputMessage)) {

    InputStream inputStream = new GZIPInputStream(

    inputMessage.getBody());

    return objectMapper.readValue(inputStream, getJavaType(clazz));

    } else {

    return super.readInternal(clazz, inputMessage);

    }

    }

    private boolean isGzipEncoded(HttpInputMessage inputMessage) {

    HttpHeaders headers = inputMessage.getHeaders();

    if (headers != null) {

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 14/59

    List contentEncodings = headers.get("Content-Encoding");

    if (contentEncodings != null) {

    for (String contentEncoding : contentEncodings) {

    if (contentEncoding != null

    && contentEncoding.toLowerCase().contains("gzip")) {

    return true;

    }

    }

    }

    }

    return false;

    }

    }

    For corresponding code see: mg-library-

    android/de.metagear.spring.web.GzipEnabledMappingJacksonHttpMessageConverter

    We're simply overwriting readInternal(..), where we're decorating the RestTemplate's InputStream by a

    java.util.zip.GZIPInputStream. Just like Spring's MappingJacksonHttpMessageConverter does, we're then letting

    Jackson's ObjectMapper process the stream.

    It's not possible by design to assign a HttpMessageConverter to the RestTemplate, thus we overwrite the latter:

    public class GzipJsonRestTemplate extends RestTemplate {

    protected List>();

    allMessageConverters

    .add(new GzipEnabledMappingJacksonHttpMessageConverter());

    allMessageConverters.addAll(super.getMessageConverters());

    }

    @Override

    public List

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 15/59

    request.getHeaders().add("Accept-Encoding", "gzip,deflate");

    }

    // ...

    }

    }

    For corresponding code see: mg-library-android/de.metagear.spring.web.GzipJsonRestTemplate

    Basically, we're adding our GzipEnabledMappingJacksonHttpMessageConverter to the top of the

    List

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 16/59

    }

    };

    Message message = Message.obtain(asyncTaskHandler, callback);

    message.sendToTarget();

    }

    @Override

    public void onHttpGetTaskSucceeded(int requestCode, List pizzas) {

    setListAdapter(pizzas); // this does not require employing a Handler

    // for some reason

    }

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    There are further ways to use Handlers and Messages, but I found this way to be most unobtrusive. For

    more information on using Handlers see the article Creating Dialogs (search and find "Example

    ProgressDialog with a second thread") and the Handler ApiDoc .

    3.3.2. Excursus: Android Activity Lifecycle

    Activities are the main building blocks of Android applications as they are for our sample application.

    While this article does not discuss any details on that, it's most important for developers to be aware of the

    Activity Lifecycle (so if you're unclear about that, you should read the linked document, at any rate).

    For instance, in the sample application, the onPause(..) method is used to stop a LocationManager from

    permanently requesting updates, the onPrepareOptionsMenu(..) method is used to enable or disable MenuItems

    depending on the Activity's current state.

    For lifecycle methods related to Menus see chapter Implementing Option Menus.

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 17/59

    3.3.3. PizzasListActivity: Faciliating Selecting From a List of Items

    3.3.3.1. Displaying the Pizzas List

    PizzasListActivity extends ListActivity, which displays a list of Views when a ListAdapter is assigned, which

    provides a custom View for each list item.

    After the List has been retrieved from the server (see chapter Retrieving the Pizzas List), such an

    adapter gets assigned to the ListActivity:

    setListAdapter(new PizzaViewAdapter(this, pizzas));

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 18/59

    3.3.3.1.1. PizzaViewAdapter

    The PizzaViewAdapter is defined as in the following code snippet (simplified for a start see chapter Caching

    Views):

    public class PizzaViewAdapter extends BaseAdapter {

    private LayoutInflater inflater;

    private Context context;

    private List pizzas;

    public PizzaViewAdapter(Context context, List pizzas) {

    this.context = context;

    this.pizzas = pizzas;

    this.inflater = LayoutInflater.from(context);

    }

    @Override

    public int getCount() {

    return pizzas.size();

    }

    @Override

    public Pizza getItem(int position) {

    return pizzas.get(position);

    }

    @Override

    public long getItemId(int position) {

    return position;

    }

    @Override

    public View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null) {

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 19/59

    convertView = inflater.inflate(

    R.layout.pizzaslist_view_tablelayout, null);

    }

    customizeView(position, convertView);

    return convertView;

    }

    protected void customizeView(int position, View view) {

    Pizza pizza = getItem(position);

    TextView nameView = (TextView) view.findViewById(R.id.pizzasList_name);

    nameView.setText(pizza.getName());

    // ...

    }

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.view.adapter.PizzaViewAdapter

    We're overriding Adapter.getView(..) to provide the ListActivity with a custom view for each list item. That

    View is inflated by a LayoutInflater from the given XML layout. In our customizeView(..) method we assign

    the current Pizza's properties to TextViews and other View widgets.

    The customizeView(..) method has been externalized from the getView(..) method in order to be

    potentially overwritten by subclasses (e.g. the PizzaLineItemViewAdapter of our shopping cart, which

    additionally displays the pizza line item's user-selected quantity, a remove pizza from list button, etc.).

    3.3.3.1.2. Root XML Layout

    The following code snippet shows the PizzasListActivity's root XML:

    The ListView's and TextView's android:ids are predefined by the Android framework and are mandantory when

    used with Android ListActivitys.

    The ListView will be used to hold the list item Views discussed in the next chapter. The TextView will be

    displayed only if the list is empty.

    3.3.3.1.3. View XML Layout

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 20/59

    The XML layout for each (Pizza) view is defined as follows:

    For corresponding code see: mg-pizzastore-android/res/layout/pizzaslist_view_tablelayout.xml

    There is a TableLayout with two TableRows. The second table row contains fewer views than the first one, so

    we're assigning an android:layout_span (that would translate to colspan in HTML tables).

    android:stretchColumns takes a comma-separated list of zero-based column indexes (or, alternatively, a * to

    stretch all columns). These columns will be stretched if the TableLayout gets wider than its cells require.

    Analogously, there is the android:shrinkColumns property.

    3.3.3.1.4. Caching Views

    Getting back to our PizzaViewAdapter.getView(..) method discussed above, that method has been simplified in

    the above code snippet for clarity.

    Actually, it caches each View's child views (i.e., TextViews) in order to avoid unnessecary calls to

    View.findViewById(int), by applying the ViewHolder pattern .

    3.3.3.2. Selecting a Pizza into the Shopping Cart

    In the PizzasListActivity's pizzas list, a pizza can be selected by clicking a list item. This functionality is

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 21/59

    realized by assigning an AdapterView.OnItemClickListener to a ListActivity's ListView:

    protected void preparePizzaClick() {

    getListView().setOnItemClickListener(

    new AdapterView.OnItemClickListener() {

    @Override

    public void onItemClick(AdapterView parent, View view,

    int position, long id) {

    Pizza pizza = PizzasListActivity.getListAdapter()

    .getItem(position);

    appState.getOrder().add(pizza);

    showPizzaSelectedDialog(pizza);

    }

    });

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    From within that overwritten onItemClick(..) method, we access the instance variable appState (of type

    ApplicationState) and, next, implement a custom AlertDialog. Both tasks are discussed in the following two

    chapters.

    3.3.3.3. Excursus: Maintaining Global Application State

    In chapter Data Model, we've discussed that our main model consists of an Order, which holds a list of

    PizzaLineItems, plus a UserData instance along with its Address.

    This model is used queried and

    edited throughout most of our

    application's activities, representing

    the application's global state. This

    state is held by our ApplicationState

    class, extending

    android.app.Application:

    public class ApplicationState extends Application {

    private Order order = new Order();

    public void setOrder(Order order) {

    this.order = order;

    }

    public Order getOrder() {

    return order;

    }

    // ...

    }

    For corresponding code see: mg-pizzastore-android/de.metagear.pizzastore.ApplicationState

    This Application is set up in AndroidManifest.xml:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 22/59

    package="de.metagear.pizzastore"

    ... >

    ...

    ...

    For corresponding code see: mg-pizzastore-android/AndroidManifest.xml

    This Application can be accessed from all activities (but also services, etc.) for instance, by the following

    code:

    ApplicationState appState = (ApplicationState) getApplication();

    appState.getOrder().add(pizza);

    3.3.3.4. Excursus: Creating Custom UI Messages

    3.3.3.4.1. Creating an AlertDialog

    From the OnItemClickListener.onItemClick(..) method, which has

    been discussed in chapter Selecting a Pizza into the Shopping Cart,

    we're opening an OK/Cancel AlertDialog:

    protected void showPizzaSelectedDialog(Pizza pizza) {

    final AlertDialog alertDialog = new AlertDialog.Builder(

    PizzasListActivity.this).create();

    alertDialog.setTitle(getResources().getString(

    R.string.pizzasList_pizzaSelectedTitle, pizza.getName()));

    alertDialog.setMessage(getResources().getString(

    R.string.pizzasList_pizzaSelectedMsg));

    alertDialog.setIcon(R.drawable.cart);

    alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getResources()

    .getString(R.string.yes),

    new DialogInterface.OnClickListener() {

    @Override

    public void onClick(DialogInterface dialog, int which) {

    PizzasListActivity.showPizzasCartListActivity();

    }

    });

    alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getResources()

    .getString(R.string.no), new DialogInterface.OnClickListener() {

    @Override

    public void onClick(DialogInterface dialog, int which) {

    alertDialog.cancel();

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 23/59

    Toast.makeText(PizzasListActivity.this,

    R.string.pizzasList_pizzaAdded, Toast.LENGTH_LONG)

    .show();

    }

    });

    alertDialog.show();

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    3.3.3.4.2. Creating a Toast Message

    When the user clicks "OK", the application displays the

    PizzasCartListActivity screen. Otherwise, if they click "Cancel",

    there's just a concise message popping up, a Toast , telling, "Pizza

    Added":

    Toast.makeText(PizzasListActivity.this,

    R.string.pizzasList_pizzaAdded, Toast.LENGTH_LONG).show();

    3.3.3.5. Implementing Option Menus

    The Activity lifecycle methods related to Menus are:

    boolean onCreateOptionsMenu(Menu)

    boolean onPrepareOptionsMenu(Menu), and

    boolean onOptionsItemSelected(MenuItem)

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 24/59

    3.3.3.5.1. Creating MenuItems

    In boolean onCreateOptionsMenu(Menu), we're creating a set of MenuItems:

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

    menuItemsMap = new HashMap();

    menuItemsMap.put(

    R.string.pizzasCart_pizzasList,

    menu.add(R.string.pizzasCart_pizzasList).setIcon(

    R.drawable.script_edit));

    menuItemsMap.put(

    R.string.pizzasList_viewShoppingCart,

    menu.add(R.string.pizzasList_viewShoppingCart).setIcon(

    R.drawable.cart));

    // ...

    return true;

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    We're adding these MenuItems to the instance variable protected Map menuItemsMap

    because subclasses (in this case PizzasCartListActivity) need to access and manipulate the entries. That

    way, we can re-use our MenuItems.

    3.3.3.5.2. Manipulating MenuItems Depending on the Current State

    boolean onPrepareOptionsMenu(Menu) is the method that is called each time just before a Menu is about to be (re-

    )drawn. This is the place to manipulate MenuItems to correspond to the current Activity state:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 25/59

    @Override

    public boolean onPrepareOptionsMenu(Menu menu) {

    setMenuItemState(R.string.pizzasCart_pizzasList, false, false);

    setMenuItemState(R.string.pizzasList_viewShoppingCart, true,

    !isShoppingCartEmpty());

    // ...

    return true;

    }

    protected void setMenuItemState(int itemTitleResID, boolean visible,

    boolean enabled) {

    MenuItem item = menuItemsMap.get(itemTitleResID);

    item.setEnabled(enabled);

    item.setVisible(visible);

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    3.3.3.5.3. Evaluating MenuItem Clicks

    We're using boolean onOptionsItemSelected(MenuItem item) to react

    on MenuItem clicks:

    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

    if (item.getTitle().equals(getString(R.string.pizzasList_viewUserData))) {

    showUserDataActivity();

    } else if (item.getTitle().equals(

    getString(R.string.pizzasCart_pizzasList))) {

    showPizzasListActivity();

    } else if ...

    }

    return true;

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    3.3.3.6. Starting Other Activities

    When the user clicks the Show Shopping Cart MenuItem, the PizzasCartListActivity is started:

    protected void showPizzasCartListActivity() {

    Intent intent = new Intent(this, PizzasCartListActivity.class);

    startActivity(intent);

    }

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 26/59

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    See chapters Passing Data to an Activity and Passing Result Data Back to the Calling Activity for

    corresponding, expanded, information.

    3.3.3.7. Re-Using Layout Styles

    Attributes that are to be commonly re-used in XML layout files can be defined in central resource files. By

    convention, the default file name is styles.xml, however that's not mandantory.

    The following code snippet shows the sample application's styles.xml:

    @drawable/customshape

    fill_parent

    wrap_content

    1

    5dip

    18sp

    bold|italic

    10dip

    wrap_content

    0dip

    wrap_content

    1

    true

    50

    For corresponding code see: mg-pizzastore-android/res/values/styles.xml

    The formField.text style inherits its attributes from the formField style, inducted by the dot-style naming

    convention.

    In XML layout files these styles are referenced by using the style attribute, for instance:

    For further information see the article Applying Styles and Themes .

    3.3.3.8. Drawing a View with Rounded Corners

    You're surely wondering how this design pope managed to draw such cool rounded edges on that "Pizzas

    List" TextView. ;-) Well, here it is:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 27/59

    android:shape="rectangle">

    For corresponding code see: mg-pizzastore-android/res/drawable/customshape.xml

    This drawable is referenced in PizzasListActivity's main layout view definition ...

    ...

    ...

    For corresponding code see: mg-pizzastore-android/res/layout/pizzaslist_linearlayout.xml

    ... which in turn references the corresponding style attribute:

    @drawable/customshape

    ...

    ...

    For corresponding code see: mg-pizzastore-android/res/values/styles.xml

    Unfortunately, Android XML Drawables are not really documented at all; nevertheless, there's a third party's

    documentation .

    Apropos visual design and best practices, we'd need to note that our sample application uses icons that

    don't comply with Android's Icon Design Guidelines .

    3.3.4. PizzasCartListActivity: Faciliating Reviewing The User'sShopping Cart

    The pizzas cart activity lists the pizzas that the user has selected into their shopping cart (represented by

    PizzaLineItems: Pizzas plus their respective user-selected quantities).

    3.3.4.1. Inheriting from PizzasListActivity

    As PizzasCartListActivity shares most of its functionality with PizzasListActivity, it extends the latter, so

    we're overriding a couple of methods.

    There are differences to PizzasListActivity in terms of which menu items to show or hide:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 28/59

    While in the PizzasListActivity the data model is a List,

    PizzasCartListActivity's data model is a List.

    Accordingly, the ListAdapter is of type PizzaLineItemViewAdapter.

    The PizzaLineItemViewAdapter extends the PizzaViewAdapter, which

    has been discussed in chapter Displaying the Pizzas List. It uses the

    same XML layout (pizzaslist_view_tablelayout.xml), however

    customizes certain elements (the quantity TextView and the

    removeButton ImageView). That's where our PizzaViewAdapter's

    protected void customize*(..) methods come in, which are

    overridden in PizzaLineItemViewAdapter:

    @Override

    protected void customizeView(T pizza, ViewHolder holder) {

    super.customizeView(pizza, holder);

    customizeQuantityTextView(holder.getQuantityView(),

    String.valueOf(((PizzaLineItem) pizza).getQuantity()));

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.view.adapter.PizzaLineItemViewAdapter

    3.3.4.2. Adding and Removing Pizzas to and from the Cart

    When the user clicks on a list item in the PizzasCartListActivity ...

    @Override

    protected void preparePizzaClick() {

    getListView().setOnItemClickListener(

    new AdapterView.OnItemClickListener() {

    @Override

    public void onItemClick(AdapterView parent, View view,

    int position, long id) {

    Pizza pizza = getListAdapter().getItem(position);

    showPizzaSelectedDialog(pizza);

    }

    });

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasCartListActivity

    ... a corresponding AlertDialog is shown.

    See chapter Creating an AlertDialog for details.

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 29/59

    From this dialog, from method showPizzaSelectedDialog(Pizza), the

    application's underlying Data Model is manipulated:

    public void add(Pizza pizza) {

    PizzaLineItem pizzaItem = getPizzaLineItem(pizza);

    if (pizzaItem != null) {

    pizzaItem.setQuantity(pizzaItem.getQuantity() + 1);

    } else {

    pizzas.add(new PizzaLineItem(pizza));

    }

    }

    For corresponding code see: mg-pizzastore-shared/de.metagear.pizzastore.model.order

    After that, the ListActivity is bound to the data model (PizzasCartListActivity.setListAdapter()), the screen

    state gets updated (ListActivity.setContentChanged()), and an OnClickListener gets newly re-attached

    (PizzasCartListActivity.preparePizzaClick()):

    protected void refreshGUI() {

    setListAdapter();

    onContentChanged();

    preparePizzaClick();

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasCartListActivity

    In effect, the new model's state (PizzaLineItem quantitys for instance) will be reflected in the GUI.

    3.3.4.3. Finally, Placing the Order

    If the user has filled in their user data, and if they've selected some pizzas into their shopping cart as well, the

    Check Out Shopping Cart menu item gets enabled in PizzasListActivity.onPrepareOptionsMenu(Menu).

    When the user clicks that menu item, and confirms the subsequent dialog,

    the application HTTP POSTs the Order instance (containing a

    List and a UserData) instance to the remote server.

    Under the hood, there are techniques involved that mostly have been

    discussed already: The HttpPostTask works equivalently to the

    HttpGetPizzasListTask, not blocking the UI thread by extending a AsyncTask

    and utilizing Spring's RestTemplate. When the task has finished

    (succeeded or failed), the UI thread is updated to inform the user.

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    For corresponding code see: mg-pizzastore-android/de.metagear.pizzastore.task/HttpPostTask

    See chapter HttpGetPizzasListTask for a more detailed discussion.

    3.3.5. UserDataFormActivity: A Validating Input Form

    When the user clicks the user data MenuItem at the pizzas list or shopping cart view, the

    UserDataFormActivity is started.

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 30/59

    This form can be divided into two areas: fields related to the user's

    location to be used with Android location-based services (LBS)

    and additional user data.

    As the sample application does provide location-based services,

    there is the AddressFormActivity, which considers the location-

    based fields only. The AddressFormActivity can (could) be used

    standalone, or for other purposes.

    From there, the UserDataFormActivity extends the

    AddressFormActivity by adding the additional user data form fields

    and the action buttons.

    3.3.5.1. XML Layout

    3.3.5.1.1. User Data

    The user data XML layout is defined as follows:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 31/59

    android:id="@+id/userInfo_phone" style="@style/formField.text" />

    For corresponding code see: mg-pizzastore-android/res/layout/userinfo_form_tablelayout.xml

    We're using a TableLayout with its android:stretchColumns attribute (that takes a zero-based, comma-

    separated, list of column indexes, and that's specifying which columns to stretch if the table is wider than its

    columns require). There's also the android:layout_span attribute on Views.

    The address layout is included from a separate file.

    Empty Views are used as spacers.

    There's the style attribute to apply a style that has been defined in /res/values/styles.xml. See chapter

    Re-Using Layout Styles for details.

    de.metagear.android.view.ValidatingEditText is a reference to a custom control of ours, which extends

    Android's EditText. See chapter ValidatingEditText for more information.

    3.3.5.1.2. Address

    The address XML layout is defined as follows:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 32/59

    For corresponding code see: mg-pizzastore-android/res/layout/address_form_merge.xml

    The merge element is a container that does not represent a View (and therefore saves some resources). It's

    used in conjunction with a parent XML layout's include declaration.

    In addition to the aforementioned custom ValidatingEditText, we're also using a custom ValidatingSpinner.

    3.3.5.2. A Custom, Optionally Selectable, Spinner

    Like a ListActivity, a Spinner is populated with List items that are

    provided via an Adapter.

    By default, there is no "unselected" state, and the first list item is

    preselected, even when the user hasn't interacted with the spinner.

    This could be solved by re-arranging the source list to provide an empty first item, however we address the

    issue in a generic, object-orientated, fashion.

    3.3.5.2.1. Decorating a SpinnerAdapter

    The OptionalSelectionSpinnerAdapter decorates Android's SpinnerAdapter implementation as follows:

    public class OptionalSelectionSpinnerAdapter extends BaseAdapter implements

    SpinnerAdapter {

    protected Context context;

    protected SpinnerAdapter adapter;

    public OptionalSelectionSpinnerAdapter(Context context,

    SpinnerAdapter adapter) {

    this.context = context;

    this.adapter = adapter;

    }

    @Override

    public int getCount() {

    return adapter.getCount() + 1;

    }

    @Override

    public Object getItem(int position) {

    if (position == 0) {

    return new TextView(context);

    } else {

    return adapter.getItem(position 1);

    }

    }

    @Override

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 33/59

    public long getItemId(int position) {

    return position;

    }

    @Override

    public View getView(int position, View view, ViewGroup parent) {

    if (position == 0) {

    return new TextView(parent.getContext());

    } else {

    return adapter.getView(position 1, null, parent);

    }

    }

    @Override

    public View getDropDownView(int position, View convertView, ViewGroup parent) {

    View newView;

    if (position == 0) {

    newView = new TextView(parent.getContext());

    } else {

    newView = adapter.getDropDownView(position 1, null, parent);

    }

    return newView;

    }

    }

    For corresponding code see: mg-library-

    android/de.metagear.android.view.OptionalSelectionSpinnerAdapter

    We might have implemented Caching Views (the ViewHolder Pattern) in the above code sample.

    3.3.5.2.2. Overwriting Spinner.setAdapter(..)

    Basically, we're overriding setAdapter(SpinnerAdapter) to decorate the given SpinnerAdapter if that's not

    already been done in the calling code:

    public class OptionalSelectionSpinner extends Spinner {

    // ...

    @Override

    public void setAdapter(SpinnerAdapter adapter) {

    if (adapter instanceof OptionalSelectionSpinnerAdapter) {

    super.setAdapter(adapter);

    } else {

    super.setAdapter(new OptionalSelectionSpinnerAdapter(context,

    adapter));

    }

    }

    }

    For corresponding code see: mg-library-

    android/de.metagear.android.view.OptionalSelectionSpinner

    3.3.5.3. Populating and Saving the Form

    3.3.5.3.1. Design and Workflow

    Please note that this section does not perfectly comply with the sample project's code.

    The following sequence diagram shows the involvement of different participants in managing the application's

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 34/59

    central UserData instance:

    Participants are the following classes:

    ApplicationState: Central instance being available to all

    Application-aware class instances within a certain session,

    holding global application state.

    It provides a UserData instance via getOrder().getUserData().

    See also chapter Maintaining Global Application State.

    UserDataFormActivity: Displays the user data form, from which

    the UserData data model gets populated.

    See also chapter UserDataFormActivity: A Validating Input

    Form.

    UserDataViewAdapter: A helper class that translates between the

    UserData model and the corresponding EditText (etc.) fields of the

    UserDataFormActivity.

    See also chapter Adapting the User Data Form to the Data

    Model.

    SharedPrefsAdapter: A helper class that persists application state

    between Application shutdowns and startups, utilizing the Android

    SharedPreferences.

    See also chapter Dealing with Android's SharedPreferences.

    There's the following workflow involved:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 35/59

    1. At application startup, the ApplicationState retrieves a UserData instance from the Android

    SharedPreferences by using the SharedPrefsManager. If no UserData has been saved previously, the

    SharedPrefsManager creates a new, empty, instance.

    2. The UserDataFormActivity, when it comes up, queries the ApplicationState for a current UserData

    instance and ...

    3. ... lets the UserDataViewAdapter initialize its EditText (etc.) values (the form fields).

    4. When the user clicks the Save button (provided that everything is valid), the UserDataFormActivity

    utilizes the UserDataViewAdapter again this time to populate a UserData instance from its Views' values.

    5. This state change gets propagated to the - globally accessible ApplicationState ...

    6. ... and for future use beyond the current session persisted to the Android SharedPreferences using

    the SharedPrefsAdapter.

    The underlying data model gets populated from Android's SharedPreferences early at application startup because

    existence and validity of the UserData is required for eventually checking out the shopping cart:

    protected void initializeUserDataFromSharedPrefs() {

    try {

    UserData userData = SharedPrefsAdapter.retrieve(this, UserData.class);

    appState.getOrder().setUserData(userData);

    } catch (Throwable t) {

    // ...

    }

    }

    For corresponding code see: mg-library-

    android/de.metagear.pizzastore.activity.PizzasListActivity

    When in the UserDataFormActivity the Save button gets clicked (and the form is valid), a UserData instance is

    created from the form values and persisted to the Android SharedPreferences:

    protected void saveUserData() {

    UserData userData = UserDataViewAdapter.fromView(rootView);

    UserDataViewAdapter.toSharedPrefs(this, userData);

    appState.getOrder().setUserData(userData);

    }

    3.3.5.3.2. Dealing with Android's SharedPreferences

    SharedPreferences are used to persist and retrieve data Strings and primitives between application

    sessions.

    In the sample application, we've standardized corresponding behavior into a separate class:

    public class SharedPrefsAdapter {

    public static T retrieve(Context context, Class valueType)

    throws JsonParseException, JsonMappingException, IOException,

    InstantiationException, IllegalAccessException {

    String key = valueType.getName();

    SharedPreferences prefs = PreferenceManager

    .getDefaultSharedPreferences(context);

    String value = prefs.getString(key, null);

    if (value == null) {

    return valueType.newInstance();

    }

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 36/59

    return new ObjectMapper().readValue(value, valueType);

    }

    public static void persist(Context context, T object)

    throws JsonGenerationException, JsonMappingException, IOException {

    String key = object.getClass().getName();

    String value = new ObjectMapper().writeValueAsString(object);

    SharedPreferences prefs = PreferenceManager

    .getDefaultSharedPreferences(context);

    Editor editor = prefs.edit();

    editor.putString(key, value);

    editor.commit();

    }

    }

    For corresponding code see: mg-library-android/de.metagear.android.util.SharedPrefsAdapter

    Amongst other options, SharedPreferences can be private to the corresponding Application, or publically

    available to other installed applications. We're using the PreferenceManager.getDefaultSharedPreferences(..)

    method, which considers private data only.

    We've already mentioned that SharedPreferences only work with Strings and primitive data types. Therefore

    we're using the Jackson/JSON ObjectMapper to marshal, resp. unmarshal, objects to, resp. from, JSON Strings.

    (See chapter Using the JSON ObjectMapper with the Model Objects for implications on the model objects.)

    When persisting data to the SharedPreferences, a kind-of-transactional SharedPreferences.Editor needs to be

    employed (see the above code snippet).

    3.3.5.3.3. Adapting the User Data Form to the Data Model

    Getting back to the UserDataFormActivity that holds the user data form, this activity connects to its underlying

    data model through employing the UserDataViewAdapter.

    In addition to connecting the view to the data model, the UserDataViewAdapter also performs validation.

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.view.adapter.UserDataViewAdapter

    For what it's worth to mention, adapters are used throughout many scopes of the sample application,

    connecting different application layers or coercing transformation of different data structures implementing

    the separation of concerns paradigm.

    For corresponding code see: mg-pizzastore-android/de.metagear.pizzastore.adapter

    For corresponding code see: mg-pizzastore-android/de.metagear.pizzastore.view.adapter

    3.3.5.4. A Custom Form Field Validation Framework

    Android does not really provide a framework to validate forms, and higher-level third-party validation

    frameworks don't seem to exist. Thus we've implemented our own one just like other people did .

    3.3.5.4.1. ViewValidator Interface

    The central interface, which all validating views must implement, is the ViewValidator:

    public interface ViewValidator {

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 37/59

    boolean validate(View view);

    /**

    * Returns a localized error message (even when validation has been successful).

    * @param caption the display name of the TextView (i.e., "ZIP Code")

    * @return localized error message; never null

    */

    String getErrorMessage(String caption);

    }

    3.3.5.4.2. TextView Validators

    Our framework provides a number of ViewValidator implementations that validate TextViews or derived classes:

    For corresponding code see: mg-library-

    android/de.metagear.android.view.validation.textview

    The MinLengthValidator checks if the TextView's text has a given minimum length. The

    MatchingTextViewValidator checks if the TextView's text matches the text of another TextView (this could be

    used with Password and Password (repeat) form fields, for instance).

    The other ViewValidators all extend RegexValidator, which is implemented as in the following code snippet:

    public class RegexValidator implements ViewValidator {

    protected Context context;

    protected Pattern pattern;

    public RegexValidator(Context context, String regex) {

    context = context;

    pattern = Pattern.compile(regex);

    }

    @Override

    public boolean validate(View view) {

    Matcher matcher = pattern.matcher(((TextView) view).getText());

    return matcher.matches();

    }

    @Override

    public String getErrorMessage(String caption) {

    return context.getString(R.string.validation_regex, caption,

    pattern.toString());

    }

    }

    3.3.5.4.3. Self-Validating Views

    Currently, our little framwork provides a TextView and an OptionalSelectionSpinner, which both self-validate at

    certain user-initiated GUI events or, alternatively, are initiated programmatically.

    Validating views are required to implement the ValidatingView interface, which is listed below:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 38/59

    /**

    * A View that validates itself by using the given

    * ViewValidator.

    * If not valid, the view shall display an error icon.

    */

    public interface ValidatingView {

    /**

    * Registers the given validator.

    *

    * @param validator

    * @param fieldDisplayNameForErrorMsg

    * Localized display field name, i.e., "City" or "Postal Code".

    */

    void setValidator(ViewValidator validator,

    String fieldDisplayNameForErrorMsg);

    /**

    * Causes the view to show or hide an error icon depending on its validity.

    */

    void flagOrUnflagValidationError();

    /**

    * Removes any error icons from the view.

    */

    void unflagValidationError();

    /**

    * Returns whether the view's value is valid.

    */

    boolean isValid();

    }

    3.3.5.4.4. ValidatingEditText

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 39/59

    The AFAIK only facility that Android provides for form validation is

    TextView.setError(CharSequence). This draws an error icon and if the

    widget gains focus a descriptive text in popup box. Passing null to

    that method unflags the error.

    Design enthusiasts will instantly realize that the theme of Android's

    rudimental form validation does not match Android's default or

    even custom theme. We'd urgently need a comprehensive, high-level, validation framework for Android.

    3.3.5.4.4.1. Performing Validation

    In our ValidatingEditText, we extend EditText, implementing our ViewValidator:

    public class ValidatingEditText extends EditText implements ValidatingView {

    private ViewValidator validator;

    private String fieldDisplayNameForErrorMsg;

    // ...

    @Override

    public void setValidator(ViewValidator validator,

    String fieldDisplayNameForErrorMsg) {

    this.validator = validator;

    this.fieldDisplayNameForErrorMsg = fieldDisplayNameForErrorMsg;

    // ...

    }

    @Override

    public void flagOrUnflagValidationError() {

    String msg = isValid() ? null : validator

    .getErrorMessage(fieldDisplayNameForErrorMsg);

    setError(msg);

    }

    @Override

    public void unflagValidationError() {

    setError(null);

    }

    @Override

    public boolean isValid() {

    return validator.validate(this);

    }

    // ...

    }

    We also register a number of listeners to react on user-initiated GUI events: an OnLongClickListener, an

    OnKeyListener (for capturing the Enter key press event), and an OnFocusChangeListener (to validate on lost

    focus events).

    For corresponding code see: mg-library-android/de.metagear.android.view.ValidatingEditText

    3.3.5.4.4.2. Setting Soft Keyboard Behavior

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 40/59

    As a side effect, we're

    also setting the EditText's

    InputType to reflect the

    preferred input scheme,

    i.e., numbers for ZIP Code

    or characters for City.

    This setting will get picked

    up by the soft keyboard (if

    available).

    protected void initInputType(ViewValidator validator) {

    if (validator instanceof EmailValidator) {

    setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);

    } else if (validator instanceof PhoneNumberValidator) {

    setInputType(InputType.TYPE_CLASS_PHONE);

    } else if (validator instanceof ZipCodeValidator) {

    setInputType(InputType.TYPE_CLASS_NUMBER);

    } else if (!isPassword()) {

    setInputType(InputType.TYPE_CLASS_TEXT);

    } else if (isPassword()) {

    setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);

    }

    setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);

    }

    protected boolean isPassword() {

    TransformationMethod method = getTransformationMethod();

    return method != null && method instanceof PasswordTransformationMethod;

    }

    For corresponding code see: mg-library-android/de.metagear.android.view.ValidatingEditText

    3.3.5.4.5. ValidatingSpinner

    In contrast to TextViews, a Spinner does not provide a setError(..)

    method and also does not provide means to show that error icon and

    error message popup box.

    We're overwriting Spinner (more specifically,

    OptionalSelectionSpinner, see chapter A Custom, Optionally Selectable, Spinner) to implement our

    ValidatingView interface, and we're drawing the error icon manually as shown below.

    3.3.5.4.5.1. Drawing the Error Icon

    By looking at Android's TextView source code, we found the error icon on disk (indicator_input_error.png)

    and copied that into our project.

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 41/59

    The following code snippet draws this Bitmap on top of the Spinner's Canvas:

    @Override

    public void draw(Canvas canvas) {

    super.draw(canvas);

    if (errorIconEnabled && !isValid()) {

    drawErrorIcon(canvas);

    }

    }

    protected void drawErrorIcon(Canvas canvas) {

    final int ICON_RIGHT_MARGIN = 40;

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),

    R.drawable.indicator_input_error);

    float left = getWidth() ICON_RIGHT_MARGIN bitmap.getWidth();

    float top = (getHeight() bitmap.getHeight()) / 2;

    // ...

    canvas.drawBitmap(bitmap, left, top, new Paint());

    }

    For corresponding code see: mg-library-android/de.metagear.android.view.ValidatingSpinner

    Note the central instance variable errorIconEnabled, which gets discussed in the following chapter.

    3.3.5.4.5.2. Events that Trigger Validation

    The error icon is shown, resp., hidden, when either the user selects a Spinner item or when validation is

    requested programmatically. Note that the Spinner View will be redrawn when the user selects an item, or

    when invalidate() is called (this ought to sound familiar to Swing developers).

    @Override

    public void setSelection(int position, boolean animate) {

    super.setSelection(position, animate);

    errorIconEnabled = true;

    }

    @Override

    public void setSelection(int position) {

    super.setSelection(position);

    errorIconEnabled = true;

    }

    For corresponding code see: mg-library-android/de.metagear.android.view.ValidatingSpinner

    @Override

    public void flagOrUnflagValidationError() {

    errorIconEnabled = true;

    invalidate();

    }

    @Override

    public void unflagValidationError() {

    errorIconEnabled = false;

    invalidate();

    }

    For corresponding code see: mg-library-android/de.metagear.android.view.ValidatingSpinner

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 42/59

    3.3.5.4.5.3. There's No Such Error Message Popup Box as a FreeLunch

    Looking at the source code that enables TextViews' error message popups, we found that this ain't re-usable

    at all. We'd have to re-implement that functionality from scratch and therefore resigned on it for now.

    A comprehensive validation framework for Android like the ones that, e.g., JavaServer Faces or the

    Spring Framework provide would be really desirable.

    3.3.6. MapViewActivity: Interacting with Maps and Location-BasedServices

    3.3.6.1. General Setup

    Because of a least one blocking Bug related to location-based services and the Android emulator,

    the functionality covered in this chapter currently cannot be used on Android 2.2 and 2.3 (anything > API level

    7). That's why our project is Android 2.1-based.

    Android projects that use MapView and related libraries need to have

    the corresponding Google APIs on the classpath. These can be

    installed via the Android SDK and AVD Manager and need to be

    referenced in the Eclipse project setup:

    The Google Maps library also needs to be setup in AndroidManifest.xml. Additionally, as it accesses Google

    services on the internet, our application needs to claim the corresponding permissions:

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 43/59

    For corresponding code see: mg-pizzastore-android/AndroidManifest.xml

    The sample application's map activities can be started via the user data form's menu.

    Our MapViewActivity extends Google's MapActivity, which is used in conjunction with Google's MapView. The

    MapActivity's main XML layout is shown in the following code snippet.

    For using the MapView control, a Google Maps API Key is required.

    The keytool mentioned in the linked article is part of the (Sun) Java SDK (i.e., not necessarily the keytool

    that may be on the PATH on Linux distributions).

    In contrast to what the linked article (and other sources on the web) tell, a Google account is not required to

    generate the API key.

    The following code snippet shows the MapViewActivity's XML layout file:

    For corresponding code see: mg-pizzastore-android/res/layout/map_linearlayout.xml

    Getting back to the menu shown in the screenshot above, there are two custom MapViewModes in the sample

    application, which provide slightly differing functionality. These are discussed in the following chapters.

    3.3.6.2. Assuring Preconditions Upon the Device's State

    The Google MapView control requires accessing the internet. From our MapViewActivity we're calling our

    AndroidDeviceUtils.isNetworkConnected(..) to assure that:

    public static boolean isNetworkConnectedElseAlert(Context context,

    Class callingClass) {

    boolean isOnline = isNetworkConnected(context);

    if (!isOnline) {

    ErrorHandler.logAndAlert(context, callingClass,

    context.getString(R.string.androidUtil_error_notOnline));

    }

    return isOnline;

    }

    public static boolean isNetworkConnected(Context context) {

    ConnectivityManager manager = (ConnectivityManager) context

    .getSystemService(Context.CONNECTIVITY_SERVICE);

    return manager.getActiveNetworkInfo().isConnected();

    }

    For corresponding code see: mg-library-android/de.metagear.android.util.AndroidDeviceUtils

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 44/59

    Additionally, location-based services require that - in our case GPS is enabled. Our application checks

    whether GPS is enabled, and if it isn't, optionally displays the corresponding Android dialog and then re-checks

    that condition:

    private static boolean isGpsEnabledElseOptionallyEnableIt(Context context) {

    boolean enabled = AndroidDeviceUtils.isGpsEnabled(context);

    if (!enabled) {

    AndroidDeviceUtils.showGpsDisabledAlert(context);

    enabled = AndroidDeviceUtils.isGpsEnabled(context);

    }

    return enabled;

    }

    For corresponding code see: mg-pizzastore-

    android/de.metagear.pizzastore.activity.MapViewActivity

    The Android Location & security settings dialog is getting called by

    starting an Activity with an implicit Intent:

    context.startActivity(new Intent(

    android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));

    For corresponding code see: mg-library-

    android/de.metagear.android.util.AndroidDeviceUtils

    3.3.6.3. Displaying a Given Location in the Map Viewer

    From the Show Map menu action, we're starting our MapViewActivity

    and display the location that bases on the data in the address

    form.

    Using our utility StringUtils.join(..), these values are translated to

    the string "Am Hohen Ufer 3A, 30159, Hannover, Germany".

    This String gets translated (geocoded) to a GeoPoint by using a

    GeoCoder:

    protected GeoPoint getGeoPointFromLocationName(String locationName) {

    Geocoder geocoder = new Geocoder(this);

    final int MAX_RESULTS = 1;

    try {

  • 3/24/2014 Documented Android Sample Application

    http://metagear.de/articles/android-sample/index.html 45/59

    List addresses = geocoder.getFromLocationName(

    locationName, MAX_RESULTS);

    if ((addresses == null) || (addresses.size() == 0)) {

    Toast.makeText(

    this,

    getResources().getString(

    R.string.mapViewer_noResultsForAddress,

    locationName), Toast.LENGTH_LONG).show();