When developing an application, it’s very helpful to use the push and pull replication in continuous mode. Everything is handled by the replicators to make sure your app and Sync Gateway always have the latest documents synced.

However, a continuous pull replication means that Couchbase Lite will be using techniques such as long polling or web sockets to check if there’s new data to fetch from Sync Gateway. This can have an impact on battery life and consequently user experience.

In this post, we will explore an alternative to continuous pull replication by using Google Cloud Messaging, Android’s push notification service.

You can check out a working example in ToDoLite Android. Let’s see how you can include GCM syncing in your application!

Enabling GCM in your app

First, let’s configure ToDoLite Android to register for Google Cloud Messaging. We’ll create a new Google API project on the developer console. Follow this guide to set it up. You should have a project number and API key. Now we can update the project’s AndroidManifest.xml with the required permissions, intent filter and service. It should look like this:

// ... // ... /* ... activities ... */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // ... // ... /* ... activities ... */

Note: Check out the example from the android developer site as well.

If the project is configured correctly, we can retrieve the device token with the project number we got when creating the app in the google developer console. Add a method in the Main Activity to retrieve the device token from GCM and save it on the user profile document for example:

void getDeviceToken() { new AsyncTask() { @Override protected String doInBackground(Void... params) { GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(getApplicationContext()); String deviceToken = gcm.register("632113338862"); Log.i("GCM", "Device token : " + deviceToken); // update user profile document return null; } }.execute(null, null, null); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void getDeviceToken ( ) { new AsyncTask ( ) { @ Override protected String doInBackground ( Void . . . params ) { GoogleCloudMessaging gcm = GoogleCloudMessaging . getInstance ( getApplicationContext ( ) ) ; String deviceToken = gcm . register ( "632113338862" ) ; Log . i ( "GCM" , "Device token : " + deviceToken ) ; // update user profile document return null ; } } . execute ( null , null , null ) ; }

Note: Device tokens on Android always start with APA91 so keep an eye out for them in LogCat 😉

Next, we have to add some code to handle an incoming notification. We subclassed the WakefulBroadcastReceiver class where the onReceive method gets called every time we receive a notification.

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Explicitly specify that GcmMessageHandler will handle the intent. ComponentName component = new ComponentName(context.getPackageName(), GcmMessageHandler.class.getName()); // Start the service, keeping the device awake while it is launching. startWakefulService(context, (intent.setComponent(component))); setResultCode(Activity.RESULT_OK); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class GcmBroadcastReceiver extends WakefulBroadcastReceiver { @ Override public void onReceive ( Context context , Intent intent ) { // Explicitly specify that GcmMessageHandler will handle the intent. ComponentName component = new ComponentName ( context . getPackageName ( ) , GcmMessageHandler . class . getName ( ) ) ; // Start the service, keeping the device awake while it is launching. startWakefulService ( context , ( intent . setComponent ( component ) ) ) ; setResultCode ( Activity . RESULT_OK ) ; } }

From there, the wakeful service kicks off the GcmMessageHandler class and performs a one-shot pull replication. The wakeful service executes even if your app is running in the background. Then your app would show the new data accordingly when opening the app.

public class GcmMessageHandler extends IntentService { /* ... */ @Override protected void onHandleIntent(Intent intent) { mIntent = intent; showToast(); Application application = (Application) getApplication(); try { URL url = new URL(BuildConfig.SYNC_URL_HTTP); Replication pull = application.getDatabase().createPullReplication(url); pull.addChangeListener(getReplicationListener()); pull.start(); } catch (MalformedURLException e) { e.printStackTrace(); } } private Replication.ChangeListener getReplicationListener() { return new Replication.ChangeListener() { @Override public void changed(Replication.ChangeEvent event) { Log.i("GCM", "replication status is : " + event.getSource().getStatus()); if (event.getSource().getStatus() == Replication.ReplicationStatus.REPLICATION_STOPPED) { GcmBroadcastReceiver.completeWakefulIntent(mIntent); } } }; } /* ... */ } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class GcmMessageHandler extends IntentService { /* ... */ @ Override protected void onHandleIntent ( Intent intent ) { mIntent = intent ; showToast ( ) ; Application application = ( Application ) getApplication ( ) ; try { URL url = new URL ( BuildConfig . SYNC_URL_HTTP ) ; Replication pull = application . getDatabase ( ) . createPullReplication ( url ) ; pull . addChangeListener ( getReplicationListener ( ) ) ; pull . start ( ) ; } catch ( MalformedURLException e ) { e . printStackTrace ( ) ; } } private Replication . ChangeListener getReplicationListener ( ) { return new Replication . ChangeListener ( ) { @ Override public void changed ( Replication . ChangeEvent event ) { Log . i ( "GCM" , "replication status is : " + event . getSource ( ) . getStatus ( ) ) ; if ( event . getSource ( ) . getStatus ( ) == Replication . ReplicationStatus . REPLICATION_STOPPED ) { GcmBroadcastReceiver . completeWakefulIntent ( mIntent ) ; } } } ; } /* ... */ }

Notice we’re using the replication change listener to get notified when it’s done and to shut down the wakeful service.

That’s all we have to do on the Android side to handle the sync notification.

Saving the device tokens

Now we can store the device token on the user profile document. Each user may be logged into more than one device at a time so we should store each one of them. The user document will look like this:

{ "_id": "profile:johnny@couchbase.com", ... "device_tokens": ["APA91K...", "APA91O..."] } 1 2 3 4 5 { "_id" : "profile:johnny@couchbase.com" , . . . "device_tokens" : [ "APA91K..." , "APA91O..." ] }

In the next section, we’ll talk about adding additional application logic to Sync Gateway using the Changes Feed.

The Sync Gateway Changes Feed

The /database/changes endpoint returns a sorted list of changes made to documents in the database. This endpoint is part of the CouchDB spec and both the Couchbase Lite Listener and Sync Gateway implement it.

It’s very easy to hook into the Changes Feed of Sync Gateway to add extra logic to the back-end such as, in our case, sending push notifications.

You can use any library that implements the changes feed api:

Out of all the parameters available in the query string, the most important ones in our case are:

feed=continuous to ensure that we get changes immediately

since=now to get the changes from the current time, otherwise it will log all changes since the database was created!

Finally, let’s focus on the the last piece of the puzzle: given a document change from the changes feed, we need to get the user profile documents “interested” in that change because they hold the device tokens.

Changes Feed → GCM

In ToDoLite, there are 3 types of documents: a profile, a list and a task. The task document holds a reference to the list it belongs to and a list has an owner and a members array.

When a task document or a list document changes we’d like to notify the owner and members of that list. There are two types of events we’d like to handle:

1. A list document change event:

get the profile document of the owner

get the profile document for each member

2. A task document change event:

get the list document it belongs to

follow the same steps as 1)

With the device tokens retrieved, the last step is to send a request to Google Cloud Messaging servers with our API Key and notification payload. You can also find many libraries on GitHub that make it simple to interact with the GCM servers:

Wrap up

Using Google Cloud Messaging to trigger data fetching from the server side can provide a great user experience without any additional battery and network usage overhead.

Follow the readme instructions to run the ToDoLite demo.

I would love to know how you are using Google Cloud Messaging notifications in your app. Let me know in the comments below!

More reading: