📜 ⬆️ ⬇️

Mobile application design patterns. Command processor

“Write the code as if it would be accompanied by a violent psychopath who knows where you live.”
Martin golding

When developing one of the projects using GooglePlacesAPI, I had a problem organizing network interaction between my Android – application and server API. Intuition and “noodles” from AsyncTask'ov suggested that there should be other ways of organizing this kind of interaction. So I came across the CommandProcessor design pattern. I want to tell you about the use of this design pattern in Android applications.

To begin with, I will describe the task that needed to be solved. It was required to write an application using the Google Places API, showing previews of any place on the map that the user chose, and then, if the user wants to get more information (for example, view more pictures), then upload pictures by the specified Id of the selected place, and show all the pictures already related to the selected location. The most obvious way for me at that time was to use AsyncTask. But after some attempts, it became clear that there must be other, more convenient ways. Using AsyncTask's was awkward because:

1) To get a preview of a place, you had to first make a request to get information about all the places that were near the place chosen by the user.
')
2) According to the received Id, form and send a request for receiving a photo preview.

3) When clicking on the preview, get all the pictures related to this place.

Thus, using AsyncTask, a kind of "waterfall" was obtained and one would have to use AsyncTask inside another. And then, googling, I found information about the Command Processor pattern, which does an excellent job with the tasks described above.

The CommandProcessor design pattern separates requests to the service from their execution. The main component of the pattern is the CommandProcessor, which manages requests, schedules their execution, and also provides an additional service, for example, storing requests for late execution or cancellation of a request. The diagram borrowed from [1] shows the relationship between the components of the pattern:



The scope of the pattern



Implementation


Now, we will consider how this pattern can be applied in the development of mobile applications, and specifically, Android – applications. There are a lot of ways to implement it; you can use the IntentService or HaMeR framework (Handler, Messages, Runnable). Let's look at how I implemented this pattern in a test application. So, the test application shows routes and a list of places that are contained in a particular route. Accordingly, we have two types of requests (commands): TracksRequest and PlacesRequest. Both classes are inheritors of the CommonRequest base class in order to be processed by our processor (CommandProcessor).

//  CommonRequest   public abstract class CommonRequest { public abstract void sendRequest(int i); public int requestId; } 

 //  PlaceRequest public class PlacesRequest extends CommonRequest{ private MessageController handler_; public PlacesRequest(MessageController handler){ this.handler_ = handler; } public void sendRequest(int id){ sendAsyncPlaceRequest(id); } } 

 //  TracksRequest public class TracksRequest extends CommonRequest { private MessageController handler_; public TracksRequest(MessageController handler) { this.handler_ = handler; } public void sendRequest(int id) { sendAsyncTracksRequest(); } } 

In the sendAsyncPlaceRequest method, all the work happens: it can be creating a URL for a request to an API, creating a new Thread, parsing a response and sending the result of the work to the controller using a Handler.

 private void sendAsyncPlaceRequest(final int id){ Thread background = new Thread(new Runnable() { @Override public void run() { String response = sendRequest(getUrl(id)); List<Place> places = new ArrayList<>(); try { places = getPlacesFromJson(response); } catch (JSONException e) { Log.e("JSONException", e.getMessage()); } handler_.sendMessage(handler_.obtainMessage(States.PLACES_WERE_FOUND,places)); } }); background.start(); } 

Next, you need to implement the CommandProcessor class, which will manage our requests and execute them:
 publicclassRequestProcessor { private UpdateCallbackListener clientActivity_; public RequestProcessor(UpdateCallbackListener clienActivity) { this.clientActivity_ = clienActivity; } //         public void execute(CommonRequest request, int id) { request.sendRequest(id); } public void updateActivity(List<? extends Result> results) { clientActivity_.onUpdate(results); } } 

Now we need a controller that, depending on the state, will send various commands to the processor. The result of the request is sent from the stream using Handler:

 public class MessageController extends Handler { private static MessageController instance = null; private RequestProcessor processor_; public void init (UpdateCallbackListener listener) { processor_ = new RequestProcessor(listener); } public static MessageController getInstance(){ if (instance == null){ instance = new MessageController(); } return instance; } public void handleMessage(Message msg) { switch (msg.what) { case States.INIT_REQUEST: CommonRequest request = (CommonRequest)msg.obj; processor_.execute(request); break; case States.REQUEST_COMPLETED: List<Result> results = (List<Result>)msg.obj; processor_.updateActivity(results); break; default: break; } } } 

In order to now return the result of the work to the activation, and call some updateUI () to update the user interface (ListView filling, drawing markers on the map, etc.) you need to define the UpdateCallbackListener interface:

 public interface UpdateCallbackListener { void onUpdate(List<? extends Result> results); } 

And implement it in our activism:

  public void onUpdate(List<? extends Result> results){ tracks_ = (List<Track>) results; TrackAdapter trackAdapter = new TrackAdapter(this,tracks_); listView_.setAdapter(trackAdapter); } 

After the result returns in response to the request (for example, a request to get all the places along this route), we need to update the assets and transfer the Place objects to the adapter. We can do this through the processor_.updateActivity (places) method, which will call onUpdate () in the activity that implemented this method. The following diagram, also taken from [1], shows the pattern dynamics:



To initiate a request, we need to create a TracksRequest object in the activation and transfer it to the controller:

  controller_ = MessageController.getInstance(); controller_.init(this); TracksRequest tracksRequest = new TracksRequest(controller_); controller_.sendMessage(controller_.obtainMessage(States.INIT_REQUEST,tracksRequest)); 

Implementing with the IntentService


Using the IntentService also perfectly allows you to implement this pattern. Consider the chart:



As a command object, you can use an Intent and transfer it to our processor. Creator is our activity, which creates a command object and passes this object to the executor, that is, to the IntentService in our case. Thus, the role of the CommandProcessor is performed by the CustomIntentService class, namely the onHandleIntent () method which, depending on the data contained in the Intent, can perform various operations. To return the result to the activation, in this case you can use the BroadcastReceiver.

Step-by-step instruction


So, to summarize, to implement this pattern, you must do the following:



The advantages and disadvantages of the pattern.


Advantages:


Disadvantages:



Conclusion


Most likely, you have already seen the implementation of the pattern in one form or another. But since the theme of the architecture of mobile applications is quite relevant, I hope the article was useful. In the future, we plan to consider several more patterns used in the development of mobile applications. Write questions in the comments, see you soon.

List of sources


[1] PATTERN-ORIENTED SOFTWARE ARCHITECTURE VOLUME 1. Douglas Schmidt, Michael Stal, Hans Rohnert, Frank Buschmann
[2] Command Revisited www.dre.vanderbilt.edu/~schmidt/PDF/CommandRevisited.pdf
Test project on github: github.com/GregaryMaster/CommandProcessor

Source: https://habr.com/ru/post/266045/


All Articles