metagear.de
A – Sort-Of – Full-Fledged Android Sample Application – Walked-Through In-Depth

August 12, 2012 · Robert Söding

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 .
If you don't want to install the sample application, straightly skip to the Sample Application section.

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
The sample application makes use of Android's Location-Based Services (LBS) and Google Maps. Due to 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 This SDK version can be installed from within the Android SDK and AVD Manager, which is included with the Android SDK:
Android SDK and AVD Manager
To run the server application, has been used (both 6.0 and 7.0 work).

Downloading, Importing, and Running the Sample Application

Download the zipped project sources, which comprise of the following Eclipse projects:
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:
Eclipse Preferences: Android SDK
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:
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 ...
Android Library Kaputt
... and newly add it:
Android Library OK
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.

Tip: Getting the android.jar Source Code

Android Sources Required
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.

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.

Data Model

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:
Model Class Diagram
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.
mg-pizzastore-shared/de.metagear.pizzastore.model

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;
        }

        // ...

        @JsonIgnore
        public boolean isValid() {
                if (StringUtils.isEmpty(street) || StringUtils.isEmpty(zipCode)
                                || StringUtils.isEmpty(city) || !country.isValid()) {
                        return false;
                } else {
                        return true;
                }
        }
}
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.

Spring-Based Backend, and Remoting

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.
Pizza Store Server: Homepage
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<Pizza> fetchPizzasJson() {
                return getAllPizzas();
        }

        @RequestMapping(
                value = "postorder", 
                method = RequestMethod.POST, 
                headers = "Content-Type=application/json")
        public @ResponseBody
        String postOrderJson(@RequestBody Order order) {
                return "OK";
        }
}
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<Pizza> getAllPizzas() method simply returns a hard-coded list.

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.

GZIP Compression

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);
                        }
                }
        }

        // ...
}
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):
<servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        ...
</servlet>
<servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
</servlet-mapping>

<filter>
        <filter-name>gzipFilter</filter-name>
        <filter-class>de.metagear.util.servlet.GzipFilter</filter-class>
</filter>
<filter-mapping>
        <filter-name>gzipFilter</filter-name>
        <servlet-name>appServlet</servlet-name>
</filter-mapping>
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.
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.

Android Client

Hey, finally the fun part is soon to come!
There are even screenshots to go! ;-)

Retrieving the Pizzas List

Overview

See the following class diagram for an overview of the main classes and interfaces involved:
Client: Remoting
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 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<Pizza>:
Pizzas List Loading
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.

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:
AsyncTask
Its main method is execute(Params...), which - simplified – calls the following methods, one after the other:
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:
private class DownloadStatesTask extends AsyncTask<Void, Void, List<State>> {    
                @Override
                protected void onPreExecute() {
                        // before the network request begins, show a progress indicator
                        showLoadingProgressDialog();
                }
                
                @Override
                protected List<State> doInBackground(Void... params) {
                        try {
                                ...

                                // Perform the HTTP GET request
                                ResponseEntity<State[]> 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<State> result) {
                        // hide the progress indicator when the network request is complete
                        dismissProgressDialog();
                        
                        // return the list of states
                        refreshStates(result);
                }
        }
}
DownloadStatesTask

Implementing a ProgressDialog

Pizzas List Loading
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();
        // ...
}
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();
                }
        }
}
mg-library-android/de.metagear.pizzastore.activity.async.AbstractAsyncListActivity

HTTP GET, and JSON Decoding, and Using the RestTemplate

In HttpGetPizzasListTask, the doInBackground(..) method is defined as follows:
@Override
protected List<Pizza> doInBackground(Void... params) {
        try {
                HttpHeaders requestHeaders = new HttpHeaders();
                List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
                acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
                requestHeaders.setAccept(acceptableMediaTypes);

                HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
                RestTemplate restTemplate = new GzipJsonRestTemplate();

                ResponseEntity<Pizza[]> responseEntity = restTemplate.exchange(uri,
                                HttpMethod.GET, requestEntity, Pizza[].class);

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

        } catch (Exception e) {
                ...
                return null;
        }
}
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.

Decoding the GZIP'ed Response

Spring's RestTemplate internally prepares a List<HttpMessageConverter>s. 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) {
                        List<String> contentEncodings = headers.get("Content-Encoding");
                        if (contentEncodings != null) {
                                for (String contentEncoding : contentEncodings) {
                                        if (contentEncoding != null
                                                        && contentEncoding.toLowerCase().contains("gzip")) {
                                                return true;
                                        }
                                }
                        }
                }

                return false;
        }
}
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<HttpMessageConverter<?>> allMessageConverters;

        protected static final boolean jacksonPresent = ClassUtils.isPresent(
                        "org.codehaus.jackson.map.ObjectMapper",
                        RestTemplate.class.getClassLoader())
                        && ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator",
                                        RestTemplate.class.getClassLoader());

        public GzipJsonRestTemplate() {
                super();

                if (!jacksonPresent) {
                        throw new IllegalStateException("Jackson not present.");
                }

                initializeMessageConverters();
        }

        protected void initializeMessageConverters() {
                allMessageConverters = new ArrayList<HttpMessageConverter<?>>();
                allMessageConverters
                                .add(new GzipEnabledMappingJacksonHttpMessageConverter());
                allMessageConverters.addAll(super.getMessageConverters());
        }

        @Override
        public List<HttpMessageConverter<?>> getMessageConverters() {
                return allMessageConverters;
        }

        @Override
        protected <T> T doExecute(URI url, HttpMethod method,
                        RequestCallback requestCallback,
                        ResponseExtractor<T> responseExtractor) throws RestClientException {
                // ...

                ClientHttpRequest request = createRequest(url, method);

                if (request.getHeaders() != null) {
                        request.getHeaders().add("Accept-Encoding", "gzip,deflate");
                }

                // ...
        }
}
mg-library-android/de.metagear.spring.web.GzipJsonRestTemplate
Basically, we're adding our GzipEnabledMappingJacksonHttpMessageConverter to the top of the List<HttpMessageConverter<?>> that gets returned by the getMessageConverters() method.
Additionally, we're adding the client HTTP request header Accept-Encoding=gzip,deflate that our server implementation requires.
Classes in the Spring Framework are seldomly well-extensible. For the GzipJsonRestTemplate, in example, we had to copy & paste three private methods (with lots of code in them) that we'd rather like to leave untouched.
Anyways, after all we can now drop-in replace the RestTemplate by our GzipJsonRestTemplate, like we do in our HttpGetPizzasListTask class' doInBackground(..) method.

Updating the User Interface

The sample application's HttpGetPizzasListTask and HttpPostTask both extends Android's AsyncTask, which opens a new, non-blocking thread internally and from there finally calls its onPostExecute(..) method. We're overriding this method to provide the calling activity (PizzasListActivity in this case) with the task result – and trigger updating its GUI.
In other words, the task thread needs to update the main GUI thread's state. And that's not allowed by default – at least not when using Android's AlertDialogs and Toast messages.
Therefore our PizzasListActivity employs an Android Handler, to which we send a Message that is assigned a custom Runnable of ours. The Handler processes its tasks asynchronously, in a MessageQueue, and finally runs the Runnable in its own thread:
public class PizzasListActivity extends AbstractAsyncListActivity implements
                HttpJsonPostTaskCaller, HttpJsonGetPizzasListTaskCaller<Pizza> {
        protected Handler asyncTaskHandler;
        // ...

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                // ...
                asyncTaskHandler = new Handler();
                // ...
        }

        // ...

        @Override
        public void onHttpPostTaskSucceeded(int requestCode, String serverMessage) {
                Runnable callback = new Runnable() {
                        @Override
                        public void run() {
                                AlertDialog dialog = WidgetUtils.createOkAlertDialog(
                                                PizzasListActivity.this, R.drawable.accept,
                                                R.string.pizzasList_shoppingCart,
                                                getString(R.string.pizzasList_orderCheckedOut), null);
                                dialog.show();

                                appState.getOrder().removePizzas();
                                showPizzasListActivity();
                        }
                };

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

        @Override
        public void onHttpGetTaskSucceeded(int requestCode, List<Pizza> pizzas) {
                setListAdapter(pizzas); // this does not require employing a Handler
                                        // for some reason
        }
}
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.

Excursus: Android Activity Lifecycle

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.

PizzasListActivity: Faciliating Selecting From a List of Items

Displaying the Pizzas List

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<Pizza> 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));
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<Pizza> pizzas;

        public PizzaViewAdapter(Context context, List<Pizza> 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) {
                        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());

                // ...
        }
}
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.).
Root XML Layout
The following code snippet shows the PizzasListActivity's root XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <View android:layout_width="fill_parent" android:layout_height="4sp" />
        <TextView android:id="@+id/pizzasList_title" style="@style/formSubHeader"
                android:text="@string/pizzasList_title" />
        <View android:layout_width="fill_parent" android:layout_height="3sp" />

        <ListView android:id="@+id/android:list" android:layout_width="fill_parent"
                android:layout_height="wrap_content" />
        <TextView android:id="@id/android:empty" android:layout_width="fill_parent"
                android:layout_height="wrap_content" android:text="" />
</LinearLayout>
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.
View XML Layout
The XML layout for each (Pizza) view is defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent" android:layout_height="wrap_content"
        android:stretchColumns="1">
        <TableRow>
                <TextView android:id="@+id/pizzasList_quantity" layout_width="15sp"
                        android:layout_height="wrap_content" android:text="qty"
                        android:paddingLeft="5sp" android:gravity="right" />

                <TextView android:id="@+id/pizzasList_name"
                        android:layout_width="0dip" android:layout_height="wrap_content"
                        android:text="pizza name" android:textStyle="bold"
                        android:textScaleX="1" android:textSize="14sp"
                        android:paddingLeft="5sp" />

                <TextView android:id="@+id/pizzasList_price"
                        android:layout_width="wrap_content" android:layout_height="wrap_content"
                        android:text="price" android:paddingLeft="5sp" android:gravity="right" />

                <LinearLayout android:orientation="horizontal"
                        android:layout_width="fill_parent" android:layout_height="wrap_content"
                        android:paddingRight="5sp" android:paddingLeft="25sp">
                        
                        <ImageView android:src="@drawable/add"
                                android:layout_width="wrap_content" android:layout_height="wrap_content" />
                        <ImageView android:id="@+id/pizzasList_removeButton"
                                android:src="@drawable/delete" android:layout_width="wrap_content"
                                android:layout_height="wrap_content" android:paddingLeft="5sp" />
                </LinearLayout>
        </TableRow>
        <TableRow>
                <TextView android:id="@+id/pizzasList_ingredients"
                        android:layout_width="0dip" android:layout_height="wrap_content"
                        android:text="ingredients" android:textStyle="italic"
                        android:paddingLeft="30sp" android:layout_span="4" />
        </TableRow>
</TableLayout>
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.
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.

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 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);
                                }
                        });
}
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.

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.
Model Class Diagram
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;
        }

        // ...  
}
mg-pizzastore-android/de.metagear.pizzastore.ApplicationState
This Application is set up in AndroidManifest.xml:
<manifest 
        package="de.metagear.pizzastore"
        ... >

        <application android:icon="@drawable/icon" android:label="@string/app_name"
                android:name="ApplicationState">
                ...
        </application>

        ...
</manifest>
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);

Excursus: Creating Custom UI Messages

Creating an AlertDialog
Alert Dialog
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();
                        Toast.makeText(PizzasListActivity.this,
                                        R.string.pizzasList_pizzaAdded, Toast.LENGTH_LONG)
                                        .show();
                }
        });

        alertDialog.show();
}
mg-pizzastore-android/de.metagear.pizzastore.activity.PizzasListActivity
Creating a Toast Message
Toast
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();

Implementing Option Menus

The Activity lifecycle methods related to Menus are:
Menu
Creating MenuItems
In boolean onCreateOptionsMenu(Menu), we're creating a set of MenuItems:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
        menuItemsMap = new HashMap<Integer, MenuItem>();

        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;
}
mg-pizzastore-android/de.metagear.pizzastore.activity.PizzasListActivity
We're adding these MenuItems to the instance variable protected Map<Integer, MenuItem> menuItemsMap because subclasses (in this case PizzasCartListActivity) need to access and manipulate the entries. That way, we can re-use our MenuItems.
Manipulating MenuItems Depending on the Current State
MenuItem 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:
@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);
}
mg-pizzastore-android/de.metagear.pizzastore.activity.PizzasListActivity
Evaluating MenuItem Clicks
Shopping Cart Menu
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;
}
mg-pizzastore-android/de.metagear.pizzastore.activity.PizzasListActivity

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);
}
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.

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:
<?xml version="1.0" encoding="utf-8"?>
<resources>
        <style name="formSubHeader">
                <item name="android:background">@drawable/customshape</item>
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_weight">1</item>
                <item name="android:paddingLeft">5dip</item>
                <item name="android:textSize">18sp</item>
                <item name="android:textStyle">bold|italic</item>
        </style>
        <style name="formFieldLabel">
                <item name="android:paddingRight">10dip</item>
                <item name="android:layout_height">wrap_content</item>
        </style>
        <style name="formField">
                <item name="android:layout_width">0dip</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_weight">1</item>
        </style>
        <style name="formField.text">
                <item name="android:singleLine">true</item>
                <item name="android:maxLength">50</item>
        </style>
</resources>
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:
<TextView style="@style/formFieldLabel" android:text="@string/userInfo_name" />
For further information see the article Applying Styles and Themes.

Drawing a View with Rounded Corners

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:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">

        <solid android:color="#3e3e3e" />

        <corners 
                android:bottomRightRadius="7dp"
                android:bottomLeftRadius="7dp" 
                android:topLeftRadius="7dp"
                android:topRightRadius="7dp" />
</shape>
mg-pizzastore-android/res/drawable/customshape.xml
This drawable is referenced in PizzasListActivity's main layout view definition ...
<LinearLayout ...>
        ...
        <TextView style="@style/formSubHeader"
                android:text="@string/pizzasList_title" />
        ...
</LinearLayout>
mg-pizzastore-android/res/layout/pizzaslist_linearlayout.xml
... which in turn references the corresponding style attribute:
<resources>
        <style name="formSubHeader">
                <item name="android:background">@drawable/customshape</item>
                ...
        </style>
        ...
</resources>
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.

PizzasCartListActivity: Faciliating Reviewing The User's Shopping 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).

Inheriting from PizzasListActivity

Pizzas Cart
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:
Pizzas List Menu
Shopping Cart Menu
While in the PizzasListActivity the data model is a List<Pizza>, PizzasCartListActivity's data model is a List<PizzaLineItem>. 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 <T extends Pizza> void customizeView(T pizza, ViewHolder holder) {
        super.customizeView(pizza, holder);
        customizeQuantityTextView(holder.getQuantityView(),
                        String.valueOf(((PizzaLineItem) pizza).getQuantity()));
}
mg-pizzastore-android/de.metagear.pizzastore.view.adapter.PizzaLineItemViewAdapter

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);
                                }
                        });
}
Pizza Selected Dialog
mg-pizzastore-android/de.metagear.pizzastore.activity.PizzasCartListActivity
... a corresponding AlertDialog is shown.
See chapter Creating an AlertDialog for details.
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));
        }
}
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();
}
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.

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).
Order Sent
When the user clicks that menu item, and confirms the subsequent dialog, the application HTTP POSTs the Order instance (containing a List<PizzaLineItem> 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.
mg-pizzastore-android/de.metagear.pizzastore.activity.PizzasListActivity
mg-pizzastore-android/de.metagear.pizzastore.task/HttpPostTask
See chapter HttpGetPizzasListTask for a more detailed discussion.

UserDataFormActivity: A Validating Input Form

User Data Menu
When the user clicks the user data MenuItem at the pizzas list or shopping cart view, the UserDataFormActivity is started.

User Data Form
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.

XML Layout

User Data
The user data XML layout is defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent" android:layout_height="fill_parent"
        android:stretchColumns="1" android:shrinkColumns="0">

        <include layout="@layout/address_form_merge" />

        <TableRow>
                <TextView style="@style/formSubHeader" android:layout_span="2" 
                android:text="@string/userInfo_userData" />
        </TableRow>
        <View android:layout_width="fill_parent" android:layout_height="4sp" />

        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/userInfo_name" />
                <de.metagear.android.view.ValidatingEditText
                        android:id="@+id/userInfo_name" style="@style/formField.text" />
        </TableRow>
        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/userInfo_phone" />
                <de.metagear.android.view.ValidatingEditText
                        android:id="@+id/userInfo_phone" style="@style/formField.text" />
        </TableRow>
        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/userInfo_email" />
                <de.metagear.android.view.ValidatingEditText
                        android:id="@+id/userInfo_email" style="@style/formField.text" />
        </TableRow>

        <View android:layout_height="2dip" />

        <TableRow>
                <View />
                <LinearLayout android:orientation="horizontal" style="@style/formField">
                        <Button android:id="@+id/userInfo_saveButton" style="@style/formField"
                                android:text="@string/userInfo_save" />
                        <Button android:id="@+id/userInfo_resetButton" style="@style/formField"
                                android:text="@string/userInfo_reset" />
                </LinearLayout>
        </TableRow>
</TableLayout>
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.
Address
The address XML layout is defined as follows:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent" android:layout_height="fill_parent"
        android:stretchColumns="1" android:shrinkColumns="0">

        <View android:layout_width="fill_parent" android:layout_height="4sp" />
        <TableRow>
                <TextView style="@style/formSubHeader" android:layout_span="2" 
                android:text="@string/address_location" />
        </TableRow>
        <View android:layout_width="fill_parent" android:layout_height="4sp" />

        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/address_street" />
                <de.metagear.android.view.ValidatingEditText
                        android:id="@+id/address_street" style="@style/formField.text" />
        </TableRow>
        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/address_zipCode" />
                <de.metagear.android.view.ValidatingEditText
                        android:id="@+id/address_zipCode" style="@style/formField.text" />
        </TableRow>
        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/address_city" />
                <de.metagear.android.view.ValidatingEditText
                        android:id="@+id/address_city" style="@style/formField.text" />
        </TableRow>
        <TableRow>
                <TextView style="@style/formFieldLabel" android:text="@string/address_country" />
                <de.metagear.android.view.ValidatingSpinner
                        android:id="@+id/address_country" style="@style/formField" />
        </TableRow>
</merge>
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.

A Custom, Optionally Selectable, Spinner

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.
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
        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;
        }
}
mg-library-android/de.metagear.android.view.OptionalSelectionSpinnerAdapter
We might have implemented Caching Views (the ViewHolder Pattern) in the above code sample.
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));
                }
        }
}
mg-library-android/de.metagear.android.view.OptionalSelectionSpinner

Populating and Saving the Form

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 central UserData instance:
User Data Sequence Diagram
User Data Form
Participants are the following classes:
There's the following workflow involved:
  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) {
                // ...
        }
}
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);
}
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> T retrieve(Context context, Class<T> 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();
                }

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

        public static <T> 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();
        }
}
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).
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.
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.
mg-pizzastore-android/de.metagear.pizzastore.adapter
mg-pizzastore-android/de.metagear.pizzastore.view.adapter

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.
ViewValidator Interface
The central interface, which all validating views must implement, is the ViewValidator:
public interface ViewValidator {
        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);
}
TextView Validators
Our framework provides a number of ViewValidator implementations that validate TextViews or derived classes:
TextView Validators
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());
        }
}
Self-Validating Views
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:
/**
 * 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(); }
ValidatingEditText
Validating EditText
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.
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).
mg-library-android/de.metagear.android.view.ValidatingEditText
Setting Soft Keyboard Behavior
Input Method: NumbersInput Method: Text
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;
}
mg-library-android/de.metagear.android.view.ValidatingEditText
ValidatingSpinner
Validating Spinner
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.
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.
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());
}
mg-library-android/de.metagear.android.view.ValidatingSpinner
Note the central instance variable errorIconEnabled, which gets discussed in the following chapter.
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;
}
mg-library-android/de.metagear.android.view.ValidatingSpinner
@Override
public void flagOrUnflagValidationError() {
        errorIconEnabled = true;
        invalidate();
}

@Override
public void unflagValidationError() {
        errorIconEnabled = false;
        invalidate();
}
mg-library-android/de.metagear.android.view.ValidatingSpinner
There's No Such Error Message Popup Box as a Free Lunch
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.

MapViewActivity: Interacting with Maps and Location-Based Services

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.
Map Activities Menu
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:
Google Libraries
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:
<manifest ...>
        <application ...>
                <activity ...>
                </activity>

                <uses-library android:name="com.google.android.maps" />
        </application>

        <uses-permission android:name="android.permission.INTERNET" />
</manifest>
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:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        
        <com.google.android.maps.MapView
                android:id="@+id/mapView" android:layout_width="fill_parent"
                android:layout_height="fill_parent" android:clickable="true"
                android:apiKey="Google Maps API Key" />
</LinearLayout>
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.

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();
}
mg-library-android/de.metagear.android.util.AndroidDeviceUtils
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;
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
Android Settings Dialog
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));
mg-library-android/de.metagear.android.util.AndroidDeviceUtils

Displaying a Given Location in the Map Viewer

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.
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 {
                List<Address> 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();
                        return null;
                }

                Address address = addresses.get(0);
                GeoPoint geoPoint = new GeoPoint(
                                (int) (address.getLatitude() * 1E6),
                                (int) (address.getLongitude() * 1E6));

                return geoPoint;
        } catch (IOException e) {
                // ...
                return null;
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
From here we can ...
mapView.getController().animateTo(geoPoint);
... and ...
mapView.getOverlays().add(new LocationIndicatorOverlay(geoPoint));
... to draw that little blue dot at our current location.
Highlighting a Location on the MapView Control
Current Coordinates
A MapView allows for adding Overlays via its getOverlays().add(Overlay) method.
Our LocationIndicatorOverlay draws a little blue dot on top of the MapView, indicating the coordinates specified by the GeoPoint passed to its constructor.
The coordinates in pixels are obtained by using the MapView's Projection; the final blue circle is drawn by using android.graphics APIs:
public class LocationIndicatorOverlay extends Overlay {
        protected GeoPoint geoPoint;

        public LocationIndicatorOverlay(GeoPoint geoPoint) {
                this.geoPoint = geoPoint;
        }

        @Override
        public void draw(Canvas canvas, MapView mapView, boolean shadow) {
                super.draw(canvas, mapView, shadow);
                
                Projection projection = mapView.getProjection();
                Point point = new Point();
                projection.toPixels(geoPoint, point);

                Paint paint = new Paint();
                paint.setARGB(250, 0, 0, 255);

                canvas.drawCircle(point.x, point.y, 5, paint);
        }
}
mg-pizzastore-android/de.metagear.pizzastore.view.map.LocationIndicatorOverlay

The Address from Map Activity

The Address from Map activity involves several tasks and actions:
Excursus: Mocking the Location in the Android Emulator
Google Maps LatLng Tooltip
For a short time, Google Maps provides the LatLng Tooltip feature that shows the current latitude and longitude at the mouse cursor (when pressing Shift).
To enable it, click the Google Maps Labs icon (currently titled, "New!") at the upper-right corner of the screen. In the following dialog, enable the LatLng Tooltip feature.
Finally, using these coordinates, the Android emulator's current location can be mocked in Eclipse's Emulator Control view.
Obtaining a Device's Current Location
To utilize the LocationManager, the proper permissions need to be set up in AndroidManifest.xml:
<manifest ... />
        ...
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        ...
</manifest>
mg-pizzastore-android/AndroidManifest.xml
Using the GPS_PROVIDER requires ACCESS_FINE_LOCATION; using the NETWORK_PROVIDER requires ACCESS_COARSE_LOCATION, only. (ACCESS_FINE_LOCATION covers ACCESS_COARSE_LOCATION.)
In our MapViewActivity we're using the Android LocationManager to determine the device's current Location (actually, the last known location as the LocationManager works asynchronously):
protected void initializeLocationManager() {
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_FINE);
        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
        
        locationProvider = locationManager.getBestProvider(criteria, true);
        lastKnownLocation = locationManager
                        .getLastKnownLocation(locationProvider);
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
The location provider in this case should always be GPS; I haven't checked other providers – which may return less accurate results.
The LocationManager's Location getLastKnownLocation(String) method is not guaranteed to immediately return a valid Location, thus we're asking the location manager to constantly request location updates in a loopback:
@Override
protected void onResume() {
        super.onResume();

        if (mapViewDisplayMode == MapViewDisplayMode.SELECT) {
                locationManager
                        .requestLocationUpdates(locationProvider, 0, 0, this);
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
In case our Activity vanishes to the background, we want the location manager to stop that task:
@Override
protected void onPause() {
        super.onPause();

        if (mapViewDisplayMode == MapViewDisplayMode.SELECT) {
                locationManager.removeUpdates(this);
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
In order to be informed when the LocationManager retrieves a Location (i.e., for the first time), the MapViewActivity implements the LocationListener interface:
@Override
public void onLocationChanged(Location location) {
        lastKnownLocation = location;
        locationManager.removeUpdates(this); // we need a location only once
        navigateTo(location);
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}

@Override
public void onProviderEnabled(String provider) {
}

@Override
public void onProviderDisabled(String provider) {
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
In the event of retrieving a Location, that Location gets decoded to a GeoPoint, which the MapView's MapController can animateTo(GeoPoint):
protected void navigateTo(Location location) {
        GeoPoint geoPoint = getGeoPointFromLocation(location);
        navigateTo(geoPoint);
}

protected GeoPoint getGeoPointFromLocation(Location location) {
        GeoPoint geoPoint = new GeoPoint((int) (location.getLatitude() * 1E6),
                        (int) (location.getLongitude() * 1E6));

        return geoPoint;
}

protected void navigateTo(GeoPoint geoPoint) {
        // ...

        final int INITIAL_ZOOM_LEVEL = mapView.getMaxZoomLevel()  – 3;

        mapView.getController().animateTo(geoPoint);
        mapView.getController().setZoom(INITIAL_ZOOM_LEVEL);

        switch (mapViewDisplayMode) {
        case SHOW:
                mapView.getOverlays().add(new LocationIndicatorOverlay(geoPoint));
                break;
        case SELECT:
                mapView.getOverlays().add(
                                new LocationSelectorOverlay(geoPoint, this));
                break;
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
Current Coordinates
Finally, we're adding our custom LocationSelectorOverlay to the MapView's List<Overlay>. It extends our LocationIndicatorOverlay, which highlights the given location as discussed in chapter Highlighting a Location on the MapView Control.
For further information see the article Obtaining User Location and the LocationManager ApiDoc. You might also want to look into the Google APIs Add-Ons' MyLocationOverlay.
Faciliating Selecting a Location in the Map
When the user clicks (taps) a point in the MapViewerActivity's map, we catch that event, reverse-geocode the corresponding GeoPoint to an android.location.Address and from there to a de.metagear.pizzastore.model.Address, which finally gets assigned to the UserDataFormActivity's EditText fields. These tasks are discussed in the following chapters:
Passing Data to an Activity
We've already discussed how to maintain global application state, and these concepts can be indeed used to pass data between activities. The Android SDK's design however plans for loose coupling via Intents, of which an instance is always assigned to an Activity, and which – the Intent – may be itself assigned extra data.
When the user clicks the Address from Map menu item, the MapViewActivity is meant to be started in MapViewDisplayMode.SELECT mode. We're attaching that information to the Intent that gets assigned to the MapViewActivity to be started:
protected void showMapView(MapViewDisplayMode mode) {
        // ...

        Intent intent = new Intent(this, MapViewActivity.class);
        intent.putExtra(INTENT_EXTRA_KEY_MAPVIEW_DISPLAY_MODE, mode);

        switch (mode) {
        case SELECT:
                startActivityForResult(intent,
                                REQUEST_CODE_SELECT_ADDRESS_IN_MAP);
                break;
        case SHOW:
                startActivity(intent);
                break;
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.AddressFormActivity
Note that AddressFormActivity is subclassed by our main UserDataFormActivity, which the user is seeing at the stage we're currently describing.
Instead of calling startActivity(Intent), we're now calling startActivityForResult(Intent, int), where we pass a unique request ID, int REQUEST_CODE_SELECT_ADDRESS_IN_MAP, used to relate the future result to our current request. – The activity result will become available in our overriden Activity.onActivityResult(int, int, Intent) method (see chapter Passing Result Data Back to the Calling Activity).
Now in our MapViewActivity, we're querying for that extra data as follows:
@Override
protected void onCreate(Bundle savedInstanceState) {
        // ...

        mapViewDisplayMode = (MapViewDisplayMode) getIntent()
                        .getExtras()
                        .get(UserDataFormActivity.INTENT_EXTRA_KEY_MAPVIEW_DISPLAY_MODE);

        switch (mapViewDisplayMode) {
        case SHOW:
                // ...
                break;
        case SELECT:
                initializeLocationManager();
                if (lastKnownLocation != null) {
                        navigateTo(lastKnownLocation);
                }
                break;
        // ...
        }

}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
Making a MapView Clickable (Tappable Actually)
In chapter Displaying a Given Location in the Map Viewer, we've been adding our custom LocationIndicatorOverlay (that draws a small blue dot at the given location) to the MapView by calling the MapView.getOverlays().add(Overlay) method.
The LocationSelectorOverlay extends LocationIndicatorOverlay and overrides the Overlay.onTap(..) method (which is pretty simple):
public class LocationSelectorOverlay extends LocationIndicatorOverlay {
        private OnLocationSelectedListener listener;

        public LocationSelectorOverlay(GeoPoint initialLocation,
                        OnLocationSelectedListener listener) {
                super(initialLocation);
                this.listener = listener;
        }

        @Override
        public boolean onTap(GeoPoint geoPoint, MapView mapView) {
                listener.onLocationSelected(geoPoint);
                return true;
        }

        public interface OnLocationSelectedListener {
                void onLocationSelected(GeoPoint newLocation);
        }
}
mg-pizzastore-android/de.metagear.pizzastore.view.map.LocationSelectorOverlay
Additionally, our LocationSelectorOverlay class holds an OnLocationSelectedListener interface interface, which our MapViewActivity implements so that we can call back its event methods.
Reverse Geocoding
At this point, the map is constructed as shown in chapter Obtaining a Device's Current Location. We're also Making a MapView Clickable (Tappable Actually).
When the user has clicked a point at the MapView, onLocationSelected(GeoPoint) gets called:
@Override
public void onLocationSelected(GeoPoint newLocation) {
        final int MAX_RESULTS = 1;

        double longitude = newLocation.getLongitudeE6() / 1E6;
        double latitude = newLocation.getLatitudeE6() / 1E6;

        Geocoder geocoder = new Geocoder(this);

        try {
                List<Address> addresses = geocoder.getFromLocation(latitude,
                                longitude, MAX_RESULTS);
                if ((addresses == null) || (addresses.size() == 0)) {
                        Toast.makeText(this, R.string.mapViewer_noResultsForGeoPoint,
                                        Toast.LENGTH_LONG);
                        return;
                }

                de.metagear.pizzastore.model.Address modelAddress = AddressAddressAdapter
                                .fromLocationAddress(addresses.get(0));

                getIntent().putExtra(INTENT_EXTRA_KEY_ADDRESS, modelAddress);
                setResult(RESULT_OK, getIntent());
                finish();

        } catch (IOException e) {
                // ...
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.MapViewActivity
First, we're reverse-geocoding the GeoPoint to an android.location.Address, which we then adapt to our own data model's Address.
Passing Result Data Back to the Calling Activity
Next, we're assigning this as extra data to an Intent, which we pass to the Activity's setResult(int, Intent) method. Finally, we call finish() to close this Activity and forward the result to the calling Activity's onActivityResult(int, int, Intent) method.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_CODE_SELECT_ADDRESS_IN_MAP
                        && resultCode == RESULT_OK) {
                Address address = (Address) data.getExtras().get(
                                MapViewActivity.INTENT_EXTRA_KEY_ADDRESS);
                AddressViewAdapter.fromAddress(rootView, address);
        }
}
mg-pizzastore-android/de.metagear.pizzastore.activity.AddressFormActivity
The AddressViewAdapter.fromAddress(View, Address) method is not discussed at this place. It populates fields within the given View with properties of the given Address. – We're using such adapters throughout our sample application to faciliate separation of concerns.

Activity Testing

Android Unit Test Cases in Eclipse
In the sample application, we're exemplarily testing our UserDataFormActivity (see chapter UserDataFormActivity: A Validating Input Form).
Android provides support for unit testing and functional testing of Activitys, where unit testing means to mock the Android environment. Functional testing means that our activity to be tested is fully instrumented with an Android environment, and that it is actually running in the Android Emulator.
We're doing functional testing – by letting our test case, UserDataFormActivityTest, extend Android's ActivityInstrumentationTestCase2.
To run the tests, in Eclipse select the mg-pizzastore-android-test project and choose Run As --> Android JUnit Test from the context menu.
Tests could also be executed from automated scripts, using the adb command line tool.
See the Android Developer's Guide on Activity Testing and a related tutorial for background information.

Setting Up the Android Manifest

The following code snippet shows our test project's AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="de.metagear.pizzastore.test" android:versionCode="1"
        android:versionName="1.0">
        <uses-sdk android:minSdkVersion="7" />
        
        <instrumentation android:targetPackage="de.metagear.pizzastore"
                android:name="android.test.InstrumentationTestRunner" />
                
        <application android:icon="@drawable/icon" android:label="@string/app_name">
                <uses-library android:name="android.test.runner" />
        </application>
</manifest>
mg-pizzastore-android-test/AndroidManifest.xml
The android:targetPackage equals our target project (mg-pizzastore-android) manifest's package.

Setting Up a Test Case

The following code snippet shows our UserDataFormActivityTest's basic setup:
public class UserDataFormActivityTest extends
                ActivityInstrumentationTestCase2<UserDataFormActivity> {
        private UserDataFormActivity activity;
        private ApplicationState appState;
        private ValidatingSpinner countriesSpinner;

        private String[] editTextTexts;
        private Country country;
        
        public UserDataFormActivityTest() {
                super("de.metagear.pizzastore", UserDataFormActivity.class);
        }
        
        @Override
        protected void setUp() throws Exception {
                super.setUp();
        
                activity = (UserDataFormActivity) getActivity();
                appState = (ApplicationState) activity.getApplication();
        
                countriesSpinner = (ValidatingSpinner) activity
                                .findViewById(R.id.address_country);
        
                editTextTexts = new String[] { "Am Hohen Ufer 3A", "30159",
                                "Hannover", "John Doe", "12345",
                                "john.doe@undisclosed-recipients.com" };
                country = new Country("Germany", "de");
        }
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest
First-off, we're extending the functional testing ActivityInstrumentationTestCase2, which causes our tested Activity to be run fully instrumented, within the Android Emulator.
In the nillary constructor, we call the super constructor by passing the target application's package name and the Class of the activity under test.
Note that this package name matches the value of the manifest package attribute of the target project's AndroidManifest.xml, but neither the tested Activity's package name, nor its fully qualified class name, nor anything else. (There happen to be misconceptions about the role of that parameter.)
In the setup() method, we're initializing the Activity's initial state.
This setup() method gets called automatically, and repeatedly, before each test*() method is run.
The following chapters discuss a number of test*() method types. The JUnit Framework by convention executes all methods with the signature public void test*().

Testing the Activity's Initial State

While not mandantory, it's considered good practice to check assumptions on the activity's initial state, which other test methods might rely on.
Not necessarily, but by convention, the name of the corresponding method may be testPreConditions().
In our case this method just asserts that the application's UserData instance is uninitialized:
public void testPreConditions() {
        assertTrue(!appState.getOrder().getUserData().isValid());
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest

Testing Form Validation

Validating EditText
In our initializing setup() method, we've set up a set of valid form field values that can be re-used, respectively, altered, in subsequently executing test methods:
// ...

private String[] editTextTexts;
private Country country;

@Override
protected void setUp() throws Exception {
        // ...

        editTextTexts = new String[] { "Am Hohen Ufer 3A", "30159",
                        "Hannover", "John Doe", "12345",
                        "john.doe@undisclosed-recipients.com" };
        country = new Country("Germany", "de");
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest
We're now assigning these values to the ValidatingViews (see chapter Self-Validating Views).
@UiThreadTest
public void testValidValues() {
        assignViewValues(editTextTexts, country);

        assertTrue(areAllValidatingViewsValid());
}

@UiThreadTest
public void testInvalidZipCode() {
        // exactly 5 digits required for "ZIP Code":
        editTextTexts[1] = "some invalid zip code";

        assignViewValues(editTextTexts, country);

        assertFalse(areAllValidatingViewsValid());
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest
In testValidValues(), valid values are assigned to the ValidatingViews (see chapter Self-Validating Views). Accordingly, these are supposed to return true from their boolean isValid() method.
In testInvalidZipCode(), we're using the same test data except of the ZIP Code ValidatingView, which is supposed to not report validity, correspondingly.
The following code snippet just shows some private helper methods called from the public test methods shown above:
private void assignViewValues(String[] editTextTexts, Country country) {
        setEditTextText(R.id.address_street, editTextTexts[0]);
        setEditTextText(R.id.address_zipCode, editTextTexts[1]);
        setEditTextText(R.id.address_city, editTextTexts[2]);

        WidgetUtils.setSpinnerSelectedItem(countriesSpinner, country);

        setEditTextText(R.id.userInfo_name, editTextTexts[3]);
        setEditTextText(R.id.userInfo_phone, editTextTexts[4]);
        setEditTextText(R.id.userInfo_email, editTextTexts[5]);
}

private boolean areAllValidatingViewsValid() {
        UserDataViewAdapter adapter = new UserDataViewAdapter();
        adapter.assignValidators(activity.getRootView());
        adapter.validateAllViews();
        
        return adapter.isAllViewsValid();
}

private void setEditTextText(int id, String text) {
        EditText editText = (EditText) activity.findViewById(id);
        editText.setText(text);
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest

Excursus: Attaching the Testing Thread to the UI Thread

In the above chapter, we've been adding the testing code to the UI thread's event queue by using the @UiThreadTest annotation:
@UiThreadTest
public void testValidValues() {
        assignViewValues(editTextTexts, country);

        assertTrue(areAllValidatingViewsValid());
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest
Equivalently, we could have used the Activity.runOnUiThread(Runnable) method.
However, like other developers, we experienced a NullPointerException without any stacktrace.
When trying to debug that Android JUnit Test in Eclipse, our breakpoints were not hit, and in contrast to executing in run mode, the test result was "success".
See chapter Updating the User Interface for a similar concept, using an Android Handler to attach code from another thread to the UI thread's event queue.

Simulating User Interaction

Save Button, Rejecting Invalid Values
When the user clicks the Save button, the data they entered may only be adapted to the data model if the values are valid.
Clicking that button in our test is programmatically invoked by using the Button.performClick() method:
@UiThreadTest
public void testSaveButtonWithValidValues() {
        UserData userData = assignViewValuesAndReturnUserDataFromView(
                        editTextTexts, country);

        Button saveButton = (Button) activity
                        .findViewById(R.id.userInfo_saveButton);
        saveButton.performClick();

        assertEquals(appState.getOrder().getUserData(), userData);
}

@UiThreadTest
public void testSaveButtonWithInvalidValues() {
        editTextTexts[1] = "some invalid zip code";

        UserData userData = assignViewValuesAndReturnUserDataFromView(
                        editTextTexts, country);

        Button saveButton = (Button) activity
                        .findViewById(R.id.userInfo_saveButton);
        saveButton.performClick();

        assertFalse(appState.getOrder().getUserData().equals(userData));
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest

Testing Activity Lifecycle State Management

Activity Lifecycle
Android activities can be paused (moved to the background) and destroyed (see chapter Excursus: Android Activity Lifecycle).
Our corresponding tests verify that the application's state doesn't get lost with these events:
public void testStatePausedAndResumed() {
        UserData userDataBeforePause = assignViewValuesAndReturnUserDataFromView(
                        editTextTexts, country);

        Instrumentation instrumentation = getInstrumentation();
        instrumentation.callActivityOnPause(activity);
        instrumentation.callActivityOnResume(activity);

        UserData userDataAfterResume = assignViewValuesAndReturnUserDataFromView(
                        editTextTexts, country);

        assertEquals(userDataBeforePause, userDataAfterResume);
}

@UiThreadTest
public void testStateDestroyedAndCreated() {
        UserData userDataBeforeDestroy = assignViewValuesAndReturnUserDataFromView(
                        editTextTexts, country);

        activity.finish();
        activity = getActivity();

        UserData userDataAfterCreate = assignViewValuesAndReturnUserDataFromView(
                        editTextTexts, country);

        assertEquals(userDataBeforeDestroy, userDataAfterCreate);
}
mg-pizzastore-android-test/de.metagear.pizzastore.activity.test.UserDataFormActivityTest

Resources

All links retrieved at the date of publication. – Initially, most links had been listed in 2011. Revised in year 2012, few links may point, or redirect, to more general documentation pages due to external site restructuring.

Primary Resources

Tools

Remoting

User Interface

Actually, not all of these resources always relate to the GUI.

Location-Based Services, and Google Maps

Activity Testing

Other Resources

Alternative Frameworks for Android

Web-Based UI Technologies

Android Market Share


Valid XHTML 1.0 Transitional Valid CSS!