A JBoss Project
Red Hat

Aerogear Guides Tutorials to help get you off and running.

AeroGear Pipes in Android

All methods on Pipe consume a CallBack. If you are calling your Pipe from an Activity or Fragment it is highly encouraged to create a static class which extends AbstractActivityCallback, AbstractFragmentCallback.

AeroGear uses a Pipe metaphor for connecting to a remote web service.

How to use it

HTTP verbs

Let’s say you have a simple webservice at http://www.server.com/developer. This service has the following API:

http verb endpoint

GET

/developer

POST

/developer

PUT

/developer/$id

DELETE

/developer/$id

and the JSON retrieved via get http verb

[
    {
        "id": "1",
        "photoURL": "http://www.gravatar.com/avatar/a796aaf10cd10acde35c4004d935ff0c.jpg?s=300",
        "twitter": "passos",
        "name": "Daniel Passos",
        "created": "1979-11-09"
    },
    {
        "id": "2",
        "photoURL": "http://www.gravatar.com/avatar/c9f69f10d588aa96f31181e758db4d24.jpg?s=300",
        "twitter": "summerspittman",
        "name": "Summers Pittman",
        "created": "1979-11-09"
    }
]

to marshals for this class

public class Developer {

    @RecordId
    private Long id;
    private String name;
    private String twitter;
    private String photoURL;

}

Creating a Pipe

PipeManager class is used to instantiate and manage a Pipe object. You don’t have to keep a reference to the result. Pipeline.get(String, Activity) will return a instance of LoaderPipe which is really a wrapper around the original Pipe object. There are also Pipeline.get methods for Fragments classes

PipeManager.config("developers", RestfulPipeConfiguration.class)
           .withUrl(new URL("http://www.server.com/developer"))
           .forClass(Developer.class);

Pipe<Developer> pipe = PipeManager.getPipe("developers");

Here is an example of using the Pipe API to retrieve the JSON payload shown earlier. Note that the library will automatically marshall the payload to your model, leaving you only with task to display the results in the UI. Further and equally important, it will respect the Activity (or Fragment) lifecycle, a common source of problem bugging Android developers as we will discuss next

pipe.read(new AbstractCallback<List<Developer>>() {
    @Override
    public void onSuccess(List<Developer> devs) {
        // Here you have a list of Developers made easy
    }

    @Override
    public void onFailure(Exception e) {
        // Oops! Something is wrong. Probably your internet is down :P
    }
});

You need to be aware of the Android Activity lifecycle. If the Application goes into the background (ex. the user gets a phone call), then any results which come in may be lost or cause an Exception if they interact with the UI of the suspended Activity. If there is a configuration change (ex. the phone was rotated) then the Activity is destroyed and a new one is created. If you used an anonymous inner class for your CallBack instance then you will lose your results when the operation completes (because anonymous inner classes have an implicit reference to the outer class).

However, AeroGear also has support for Android’s Loader API. Loader’s were introduced by Android in version 3.0, and their lifecycle is managed by Android. When an Activity is paused and resumed, any activity the Loader was working on is returned. If the Activity is destroyed because of a configuration change then the Loader will provide its results to the new Activity instance without having to make second call to a remote server.

The pipe instance will be respectful of the Android lifecycle and calls to its methods will be handled by and behave like Loaders. For instance, if the device is rotated the result of the read will be returned from a local cache instead of being fetched from the web again. If you want to force a read, just call pipe.reset() before your read.

PipeManager.config("developers", RestfulPipeConfiguration.class)
           .withUrl(new URL("http://www.server.com/developer"))
           .forClass(Developer.class);

LoaderPipe<Developer> pipe = PipeManager.get("developers", activityInstance);
pipe.reset();
pipe.read(new ReadDevelopersActivityCallback());

public static class ReadDevelopersActivityCallback
        extends AbstractActivityCallback<List<Developer>> {
    @Override
    public void onSuccess(List<Developer> devs) {
        // Here you have a list of Developers made easy
    }

    @Override
    public void onFailure(Exception e) {
        // Oops! Something is wrong. Probably your internet is down :P
    }
}

Plugging into the Pipe API

PipeHandler

AeroGear on Android uses a class called Pipe to retrieve data from a source asynchronously. A Pipe has the methods read, readWithFilter, remove, and save. A Pipe implementation is responsible for managing PipeHandler instances, processing their results and returning the results to the user via callbacks provided by the Pipe CRUD methods. AG Android has two Pipe implementations: RestAdapter and LoaderAdapter.

PipeHandler instances are responsible for connecting to a remote source, sending a request, fetching the response, and returning a deserialized instance of that result to the Pipe which requested it. PipeHandler do not need to worry about threading, this is the responsibility of the Pipe.

So why separate Pipe and PipeHandler? The logic of threading is troublesome and often leads to bugs. The patterns and trade offs are usually specific to Android and not your application. Since AeroGear provides this logic along with methods for selecting the most appropriate mechanisms for handling threads, there is no reason to burden a developer with it. The methods for connecting to remote services are much more specific to the use case (IE the app). If our default implementations do not fit your needs, it is much simpler to implement a PipeHandler and allow a Pipe to manage the threading for you.

For many of the cases, writing an adapter to a remote source which AG can not support is as simple as implementing a PipeHandler and passing it to a Pipe.

config.pipeHandler(new MyPipeHandle())

RequestBuilder, ResponseParser

Pipe uses PipeHandler to interact with services. The default PipeHandler is RestRunner, the RestRunner delegates requests for GsonRequestBuilder and response parse to GsonResponseParser

GsonRequestBuilder, GsonResponseParser

Behind the scenes, GsonRequestBuilder and GsonResponseParser uses Google’s GSON for JSON object serialization and deserialization. Both have a construction to consume a GSON instance. This GSON will be used to marshall and unmarshall objects. If you have nested, typed collections, etc. You can configure a GSON which supports your data model and pass it to the GsonRequestBuilder and GsonResponseParser

Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();

PipeManager.config("developers", RestfulPipeConfiguration.class)
           .withUrl(new URL("http://www.server.com/developer"))
           .requestBuilder(new GsonRequestBuilder())
           .responseParser(new GsonResponseParser(gson))
           .forClass(Developer.class);

Nested Data in Result

Sometimes you will have a simple result format, but your data will be surrounded by metadata. Take this JSON snippet for example:

{
    "data": {
        "after": "t3_17i1lt",
        "before": null,
        "children": [
                {"data":"data1"},
                {"data":"data2"},
                {"data":"data3"},
                {"data":"data4"}
        ]
    }
}

In this example you are interested in the data object’s "children" collection. Instead of writing code using GSON to fetch it, you can instead configure a GsonResponseParser and MarshallingConfig.

MarshallingConfig marshallingConfig = new MarshallingConfig();
marshallingConfig.setDataRoot("data.children")

ResponseParser responseParser = new GsonResponseParser();
responseParser.setMarshallingConfig(marshallingConfig);

PipeManager.config("developers", RestfulPipeConfiguration.class)
           .withUrl(new URL("http://www.server.com/developer"))
           .responseParser(responseParser)
           .forClass(Developer.class);

Multipart Upload

The multipart upload is a good example to use RequestBuilder and ResponseParser. In the most of the cases you need to send a file to server and receive a JSON with response. In this case you don’t need to create a new Handler, just set a new RequestBuilder in PipeConfig for RestRunner

We already have a RequestBuilder for multipart upload the MultipartRequestBuilder

Model

public class Developer {

    @RecordId
    private Long id;
    private String name;
    private File photo;

}

How to use MultipartRequestBuilder

PipeManager.config("developers", RestfulPipeConfiguration.class)
           .withUrl(new URL("http://www.server.com/developer"))
           .requestBuilder(new MultipartRequestBuilder())
           .forClass(Developer.class);

PipeManager.get("developers", activity)
           .save(developerInstance, callbackInstance);

Take a look at the complete example in our cookbook app

Feel free to create new PipeHandlers, RequestBuilder and ResponseParser and send them to the project ;)

redhatlogo-wite