Realtime sync with AeroGear on Android

Posted By Hoyt Summers Pittman

AeroGear 1.2.0 included support for AeroGear Unified Push. Part of the technology underpinning that feature on Android are the Registrar APIs. These APIs make it very easy to create a message generating or message consuming Object and integrate it with your applications in Android. In my own time I have experimented with a MQTT PushRegistrar and a WebSocket PushRegistrar. Using the my WebSocket Push Registrar I created a very simple text sync demo.

As a quick bit of background, I also wrote the server component of the demo. It runs on vert.x and persists data to MongoDB. I implemented Neil Fraser’s Differential Synchronization process on both the client and server side. This algorithm handles merging the documents various states across devices. It is also very well written and easy to follow.

In this demo a user loads a document (using a RESTful Pipe). In the callback, the Activity registers itself as a MessageHandler with the Registrar API, creates a configuration object which describes a WebSocketPushRegisrar and passes this configuration into the Registrations class which creates a new Web Socket and connects it to the server. Now when the document is changed by a different application, a diff is received on the WebSocket and its contents are given to the Activity. If the Activity edits the document it creates a diff and saves it to the server. The server is in charge of synchronizing diffs across the different devices.

CRUD operations are performed using two Pipes. A Pad Pipe which is responsible for creating documents, getting lists of documents, and getting an individual document. The second Pipe is a PadDiff Pipe. Its job is to send diffs to the server.

//Create a Pad Pipe
PipeConfig padConfig = new PipeConfig(URL, Pad.class);
pipeline.pipe(Pad.class, padConfig);

//Use the Pad Pipe
//By passing in the padListFragment to  Pipeline#get we are telling Aerogear to 
//wrap the Pipe in a Loader.

public void getPads(GetPadsListFragment getPadsListFragment, PadCallback padCallback) {
    pipeline.get("pad", getPadsListFragment, this).read(padCallback);
}

//Create a PadDiff pipe.  PadDiff pipes are 1:1 with the "Session" which is a identifier for the web socket.
//In this app a session is killed with the fragment is put into the background.  Thus I decided
//to make a PadDiff Pipe 1:1 with the fragment.
public Pipe<PadDiff> padDiff(PadFragment padFragment, String sessionId) {
    PipeConfig config = new PipeConfig(URL, PadDiff.class);
    config.setName("padDiff");
    config.setEndpoint("/padDiff/" + sessionId);
    pipeline.pipe(PadDiff.class, config);
    return pipeline.get("padDiff", padFragment, this);
}

From the devices POV the documents are fetched using the following code:

ReadFilter filter = new ReadFilter();
//Currently the way to get an element by ID is to use the LinkUri.  
//This is scheduled to be fixed in a future version of Aerogear
filter.setLinkUri(URI.create("/" + padId));
pipeline.get("pad", fragment, this).read(filter, new PadCallback(padId));

In `PadCallback` the Pad is loaded and a WebSocket is created. When the websocket creation is finished, the server sends a message to the Activity with a session_id object. This message is received on the onMessage method of the Activity. This method is also responsible for handling error messages and handling diff messages which are updates from other clients.

First we handle the response from getting the pad:

//Called from PadCallback#onSuccess
public void handlePads(List<Pad> pads) {
    pad = pads.get(0);
    setupEditor(pad);
    PushConfig pushConfig = new PushConfig();
    pushConfig.setPushServerURI(URI.create("ws://10.0.2.2:8080/pad/" + pad.getId()));

    //This will open the web socket. 
    ((AeropadApplication) getActivity().getApplication()).register(pushConfig);
}

Then the Activity receives a callback from the WSPushRegistrar with a session id. The server will send different messages depending on what information it is sending to the client. In AeroGear a MessageHandler(in this case our Activity) receives a message from the WSPushRegisrar and extracts the content based on if there is a “diff”, “session_id”, or “error” object in the response. These keys are specific to this server but the general principle can be applied to any application.

@Override
public void onMessage(Context context, Bundle message) {
    try {
        String json = message.getString(WebsocketMessageReceiver.PAYLOAD);
        JSONObject object = new JSONObject(json);
        if (object.has("diff")) {
            handleDiff(object);
        } else if (object.has("session_id")) {
            //Create a PadDiff Pipe
            diffPipe = ((AeropadApplication)getActivity().getApplication()).padDiff(this, object.getString("session_id"));
        } else if (object.has("error")) {
            Log.e("ERROR", json);
            Toast.makeText(getActivity(), json, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(getActivity(), json, Toast.LENGTH_LONG).show();
        }
    } catch (JSONException e) {
        Log.e("MESSAGE", e.getMessage(), e);
    }
 }

The full source code for the Android application can be found at my Github. It is build using the aar branch on the official AeroGear Android repository.

Oct 29th, 2013

No Comments! Be The First!

Leave a Reply