Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Latest commit

 

History

History
274 lines (193 loc) · 16.1 KB

pipe.asciidoc

File metadata and controls

274 lines (193 loc) · 16.1 KB
layout title
basic
AeroGear Android User Guide

AeroGear Pipes in Android

Pipe

AeroGear uses a Pipe metaphor for connecting to a remote web service. 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, AbstractFragmentActivityCallback, AbstractFragmentCallback, or AbstractSupportFragmentCallback.

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

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 equal important, it will respect the Activity (or Fragment) lifecycle, a common source of problem bugging Android development as we will discuss next

The developer JSON retrieved via get http verb

[
    {
        "id": "1",
        "photoURL": "https://pbs.twimg.com/profile_images/378800000054312650/2ed585582b38130e65b772b9ff787c09.jpeg",
        "twitter": "passos",
        "name": "Daniel Passos"
    },
    {
        "id": "2",
        "photoURL": "https://pbs.twimg.com/profile_images/2936447891/ebe9da7e1797936b921fa056404d52a3.jpeg",
        "twitter": "summerspittman",
        "name": "Summers Pittman"
    }
]

marshals to this class

public class Developer {

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

}

How? Pipeline class is used to instantiate and manage a Pipe object. Pipeline.pipe creates and returns an instance of Pipe. 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, FragmentActivity and Fragment (Android’s support library) classes

public class MyActivity extends ListActivity {

    private Pipeline pipeline;

    protected void onCreate() {
        String serverURL = "http://www.server.com";
        pipeline = new Pipeline(serverURL);
        pipeline.pipe(Developer.class);
    }

    protected void onStart() {

        // By default, Pipeline.pipe stores the Pipe created by Pipeline.pipe
        // using a key which is the lowercase of the class the Pipe is marshaling.
        // and append the same with endpoint in url
        LoaderPipe<Developer> pipe = pipeline.get("developer", this);
        pipe.read(new MyCallback());
    }

    private static class MyCallback extends AbstractActivityCallback<List<Developer>> {

        /*
        * AbstractActivity's constructor consumes a varargs of Objects which it uses
        * to generate the value returned by hashCode();
        */
        public MyCallback() {
            super("myCallback");
        }

        public void onSuccess(List<Developer> data) {
            ListActivity activity = (ListActivity) getActivity();
            activity.setListAdapter(new ArrayAdapter(activity,
                android.R.simple_list_item_1, data));
        }

        public void onFailure(Exception e) {
            // Gracefully handle the exception
        }

    }

}

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.

Configuring the Pipe

Often a webservice will not quite match up to the default patterns supported by AeroGear. For instance, the data may not be root element of a JSON response, you may need to specify a specific endpoint, etc. For these instances we use the PipeConfig object.

protected void onCreate() {

    String serverURL = "http://www.server.com";

    PipeConfig pipeConfig = new PipeConfig(new URL(serverURL), Developer.class);
    pipeConfig.setEndpoint("developers.json");

    Pipeline pipeline = new Pipeline(serverURL);
    pipeline.pipe(Developer.class, pipeConfig);

}

The full set of options you can configure on PipeConfig are on the javadoc page.

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 three Pipe implementations: RestAdapter, LoaderAdapter and SupportLoaderAdapter.

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 via PipeConfig.setHandler.

So let’s say all of the data we want isn’t stored in a remote server but a local file. We can easily write a PipeHandler to read from this file. For purposes of this example, let’s pretend the file is read only.

PipeHandler

public class FilePipeHandler implements PipeHandler<Developer> {

    public FilePipeHandler(Context applicationContext) {
    }

    @Override
    public List<Developer> onRead(Pipe<Developer> requestingPipe) {
        // Read file, parse JSON and return a List of Developers
    }

    // Other methods

}

How to use the new PipeHandle

URL fileURL = this.getFilesDir().toURI().toURL();

Pipeline pipeline = new Pipeline(fileURL);
PipeConfig pipeConfig = new PipeConfig(fileURL, Developer.class);

pipeConfig.setHandler(new FileHandler(getApplicationContext()));
pipeline.pipe(Developer.class, pipeConfig);

LoaderPipe<Developer> developerLoaderPipe = pipeline.get("developer", this);
developerLoaderPipe.read(new myCallback);

See the complete implementation in cookbook app

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

GSON

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

PipeConfig config = new PipeConfig(serverURL, MyModel.class);
config.setRequestBuilder(new GsonRequestBuilder(gson));
config.setResponseParser(new GsonResponseParser(gson));

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

PipeConfig pipeConfig = new PipeConfig(serverURL, MyModel.class);
pipeConfig.setResponseParser(responseParser);

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 twitter;
    private String photoURL;
    private InputStream photo;

}

How to use MultipartRequestBuilder

String serverURL = "http://www.server.com";

PipeConfig pipeConfig = new PipeConfig(serverURL, Developer.class);
config.setRequestBuilder(new MultipartRequestBuilder<Developer>());

Pipeline pipeline = new Pipeline(url);
pipeline.pipe(Developer.class, pipeConfig);

LoaderPipe<Developer> pipe = pipeline.get("developer", this);
pipe.save(developerInstance, myCallback);

Feel free to create new Handlers, RequestBuilders, ResponseParsers and send them to the project ;)