A colleague of mine, Jack Cox, wrote an article recently about managing complex mobile transactions. The article is written with an iOS slant but I found it very interesting and wondered if the paradigm could be implemented in Android and whether it could be effective. After sitting down and doing some brain storming, I realized the pattern lent itself very well to Android.
Let's review some of the main components of Jack's pattern:
- Controllers -- Typically view controllers, responsible for requesting data, processing results and presenting them to the user.
- Commands -- Encapsulates everything needed to make a service call including the data for the call itself.
- Exception listeners -- Responsible for taking action on any non-expected exception thrown from the service tier call.
- Command Queue -- Responsible for managing network requests. Controllers will place requests onto the Command Queue and listen for responses. Exception listeners will also listen for unexpected responses and deal with them appropriately.
This paradigm really lends itself well to Android's own Command Dispatch pattern implemented by the use of Intents, Services, and Broadcast Receivers.
An Android IntentService will be used as a RESTful service delegate, hiding communication details from components that need to communicate with back end services. The IntentService is also the Android representative of the iOS NSOperationQueue (command queue) in this pattern. Intents will be used as the Command object in this interaction and meshes well with their current use within the Android operating system. BroadcastReceivers will play the part of exception listeners and will also be leveraged by Activities who are the Android representative of an iOS View Controller. The implementation details do change slightly due to the differences in operating systems and will be covered in subsequent sections. Intent Filters based off of data in the Command Intent, will allow for targeted broadcast notifications of RESTful service results (or failures). We'll follow a convention over configuration pattern for targeting specific broadcast receivers with results from service calls.
Operation.Request and Operation.Response (Data)
Marshallable and unmarshallable data objects representing an operation request and response will need to be created for each RESTful operation. These data objects will also need to be Parcelable so the operating system can use interprocess communication (IPC) if necessary. These data objects will extend from 2 abstract classes, OperationRequest and OperationResponse, that will serve as marker interfaces, have common attributes across all services, as well as to enforce certain contractual needs required by the Rest Delegate Service.
Intents are used as the Command objects and will contain all the information necessary to make the network call, the results of that network call, and routing information based off the Intent metadata The action and category metadata for the Intent is different throughout the lifecycle and is dependent on where in that chain as to what the value is.
Rest Delegate Service Intents – Are explicit intents and will contain the following extras:
- "rds.service_request" – The OperationRequest and should contain everything necessary for the Rest Delegate Service to make a RESTful call to the service tier and formulate a response that can be broadcast and understood by all Broadcast Receivers.
Broadcast Receiver Intents – Are implicit intents and will identify their consumers by a combination of the Intent's action and category metadata. The action will be a static string, "me.ericmiles.mobiletrans.ACTION_REST_RESULT". The category will be one of three things: the fully qualified name of the response type, ie "me.ericmiles.mobiletrans.ops.LoginOperation.Response" , the fully qualified name of the exception caught during service communication, ie "org.springframework.web.client.RestClientException", or the default exception category if no Broadcast Receivers would react to a caught exception "me.ericmiles.mobiletrans.UNKNOWN_EXCEPTION". The category is the key component to allow broadcast receivers to filter out intents they do not care about. This will be discussed in more detail in the Intent Filter section. Broadcast Receiver Intents can have the following extras:
- "rds.service_request" – The OperationRequest that was used to make this service call. It is sent in the Result intent in case an exception Broadcast Receiver wishes to try the request again.
- "rds.service_response" – The OperationResponse received from the RESTful call if one is returned.
- "rds.http_response_code" – The HTTP response code if one is returned from the RESTful call. May be used in processing by broadcast receivers.
- "rds.exception" – The exception if one was caught.
Broadcast Receivers and Intent Filters (Listeners)
Broadcast Receivers will be used as listeners for RESTful service operation responses. These Broadcast Receivers could be registered programmatically within an Activity or can be registered statically via the Android manifest. Programmatically registered Broadcast Receivers will generally be for a specific operation for an Activity. Statically registered Broadcast Receivers will generally function as exception listeners, handling unexpected errors that transpire during RESTful communication. Broadcast Receivers extend from the Android BroacastReceiver class.
Intent filters are used throughout the Android ecosystem by Activities, Services, and Broadcast Receivers to filter Intents they are only interested in. In our communication pattern, we want to encapsulate logic to handle specific responses in their own broadcast receivers, be it regular RESTful responses or exception handlers.
Normally broadcasts are sent across the entire device, allowing any components that has registered for an Intent to receive it. As a protective measure, to prohibit outside applications registering for our RESTful response broadcasts, we will require an Android permission to be used for all recipients of the implicit Intents used for operation responses. This permission will also be designated with the "signature" protection level, meaning that only applications that have identified using this permission and having been signed with the same certificate as the defining application can receive this intent. This will rule out any other application from being able to receive these Intents unless they somehow manage to get ahold of the signing certificate (keep those secure!!!)
Rest Delegate Service (Command Processor)
The Rest Delegate Service is responsible for RESTful communication aspect of the communication pattern. The Rest Delegate Service is an Android IntentService; it is a special Android service that runs on an OS managed thread separate from the main UI thread. This will allow the service to handle network communication requests without blocking the user's interaction with the UI. The Rest Delegate Service will leverage the Spring Android framework for RESTful service communication and will leverage the Gson library for marshaling and unmarshalling JSON data under the covers.
When a response is from the RestTemplate, the RestDelegateService will package up the OperationResponse, the original OperationRequest, and the HTTP response code into an implicit Intent, and fire a broadcast. This broadcast will allow any interested parties to listen for the response as there may be more than one. For example, upon successful login, not only with the MainActivity that originally fired the Command be interested, so will the Session Manager as it needs all the session information returned from the Authentication REST service. The category for the Intent will also used the fully qualified name of the OperationResponse so appropriate Intent Filters can be applied by the operating system.
If an exception is caught, there will be no OperationResponse nor will there be an HTTP response code. Instead of placing those items into the Intent, the fully qualified name of the Exception caught will be used as the category. This will allow specific Broadcast Receivers to act as exception handlers for pre-defined errors. A default, catch all will be used in case there is no match.
Activities (View Controllers)
An Activity is the controller in the communication pattern outlined in Jack's blog entry. It is responsible for managing user interactions and what transpires during those interactions. In the Android implementation of the pattern, Activities that have RESTful service interaction will be responsible for firing off Service Intents for the Rest Service Delegate and handling those responses by managing a Broadcast Receiver that is programmatically registered for those responses with appropriate Intent Filters.
I think this pattern lends itself really well in the Android environment as you can see by the attached sample project. Interaction with the user gets a little tricky with the Exception listeners, particularly if you want to interact via Dialog boxes, however a there are numerous ways to skin a cat and it can be done. Check out the linked Github project and let me know your thoughts on the implementation.
I've included Mockey as my stubbed out service tier; it's a pretty configurable, lightweight utility to create stubs for your service tier. If you care to use it, once started you'll need to update the rest_endpoints.xml resource file with the appropriate IP address Mockey is running on, so your deployed application can interact with the service. Also, I'm unsure why Mockey doesn't persist some of the configurations, but you'll need to do the following:
- For the AuthenticationService's Default scenario, also make it the "Universal Scenario Error Response".