Tutorials

Android Push Notifications tutorial - Direct device registration

Ably can deliver native Push Notifications to Android devices using Firebase Cloud Messaging. Native Push Notifications, unlike our channel-based Pub/Sub messaging, do not require the device to maintain a connection to Ably, as the underlying platform or OS is responsible for maintaining its own battery efficient transport to receive Push Notifications. Therefore, native Push Notifications are commonly used to display visual notifications to users or launch a background process for an app in a battery efficient manner.

In this tutorial, we’ll see how to set up and send Push Notifications to your Android app using Ably’s Push Notification service.

Ably supports two types of client permissions for access to the Push Notification service:

  • push-subscribe: A client with this permission is a push target device, and it can manage the registration and subscription for itself. However, it won’t be able to manage push registrations or channel subscriptions for any other device.
  • push-admin: A client with this permission has full access to the Push Admin API and can manage registrations and subscriptions for all devices.

Read more about permissions (also called capabilities) in our API documentation.

To enable Push Notifications on your device, it must be registered with FCM first. This can be done in two ways; you can either have the device register itself directly with Ably or delegate the registration work to your app server, which would then register the device with Ably on its behalf. In this tutorial we’ll implement direct device registration. You can jump to another tutorial if you’d rather have your server register the device with FCM.

Step 1 – Set up a free account with Ably

In order to run these tutorials locally, you will need an Ably API key. If you are not already signed up, you should sign up now for a free Ably account. Once you have an Ably account:

  1. Log into your app dashboard
  2. Under “Your apps”, click on “Manage app” for any app you wish to use for this tutorial, or create a new one with the “Create New App” button
  3. Click on the “API Keys” tab
  4. Copy the secret “API Key” value from your Root key and store it so that you can use it later in this tutorial

    Copy API Key screenshot

Step 2 – Enabling Push in your Ably app

Create a sandbox application as shown below and make sure that both the Push Admin and Push Subscribe permissions are checked, for your API key. You can do this under the settings tab of your app in the developer dashboard.


Setting up a Sandbox app


Adding Push capabilities

Next, add a channel rule for your chosen channel namespace by checking the ‘Push notifications enabled’


Adding channel rules

Step 3 – Registering your app with FCM

In order to be able to use the Firebase Cloud Messaging service, you’ll need to register your app with FCM. To do this, first log into the Firebase developer console, create a new project or open an existing one, then register your app as shown below:


Adding Firebase to your app

Under your Firebase project settings, click on the Cloud Messaging tab. You should be able to see your FCM server key as shown below.


FCM server key

Copy this key and paste it in ‘Setting up Google/Firebase Cloud Messaging’ section under the Notifications tab of your Ably app dashboard and click save.


Setting up FCM on Ably dashboard

Step 4 – Adding the Ably client library to your Android project

To start using Ably in your Android app, you need to include the Ably Client library. We recommend that you include the latest client library via Gradle in your module-level gradle.build file.

apply plugin: 'com.android.application'
...
dependencies {
    ...
    compile 'io.ably:ably-android:1.1.0'
}

In the above example a specific version of the library is referenced, you can always check which is the latest stable version and always use that.

After you add the necessary dependencies, you can import the AblyRealtime class into your code and initialize it as shown below:

import io.ably.lib.realtime.AblyRealtime;

public class MainActivity extends AppCompatActivity {

    public static final int SUCCESS = 0;
    public static final int FAILURE = 1;
    public static final int UPDATE_LOGS = 2;

    public static final String STEP_1 = "Initialize Ably";
    public static final String STEP_2 = "Activate Push";
    public static final String STEP_3 = "Subscribe Channels";
    public static final String STEP_4 = "Send Test Push";

    public static final String TEST_PUSH_CHANNEL_NAME = "test_push_channel";
    //Ensure that ngrok is setup, or modify xml/network_security_config.xml accordingly.
    private static final String PRIVATE_SERVER_AUTH_URL = 'YOUR SERVER URL' + "/auth";
    private AblyRealtime ablyRealtime;

    private String getClientId() {
        String clientId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
        return clientId;
    }

    private void initAblyRuntime() throws AblyException {
        ClientOptions options = new ClientOptions();
        options.environment = BuildConfig.ABLY_ENV;
        options.key = BuildConfig.ABLY_KEY;
        //Token auth via server
        options.authUrl = PRIVATE_SERVER_AUTH_URL;
        options.authParams = new Param[]{new Param("clientId", getClientId())};

        ablyRealtime = new AblyRealtime(options);
        ablyRealtime.setAndroidContext(getApplicationContext());
        ablyRealtime.connect();
        ablyRealtime.connection.on(new ConnectionStateListener() {
            @Override
            public void onConnectionStateChanged(ConnectionStateChange state) {
                logMessage("Connection state changed to : " + state.current.name());
                switch (state.current) {
                    case connected:
                        /* Perform device registration and subscription, this is described later in the tutorial */
                        break;
                    case disconnected:
                    case failed:
                        //handle failure
                        break;
                }
            }
        });
    }
}

Alternatively, you can add your API key in the local.properties file in order to prevent the secret keys from being accidentally committed to the version control system that you use. Further, assign the ably.env variable to either ‘sandbox’ or ‘production’ accordingly. However, storing API keys on the client side makes it prone to compromise and hence is not recommended.


Setting local properties in the android app

Step 5 – Adding the Google Services Gradle plugin

To build your own Android Project, please visit the Android Developers website and get familiar with steps necessary to set up your own application.

As part of enabling Google APIs or Firebase services in your Android application you may have to add the google-services plugin to your build.gradle file:

dependencies {
    classpath 'com.google.gms:google-services:4.2.0'
    // ...
}

The google-services plugin has two main functions:

  • Process the google-services.json file and produce Android resources that can be used in your application’s code.
  • Add dependencies for basic libraries required for the services you have enabled. This step requires that the apply plugin: ‘com.google.gms.google-services’ line be at the bottom of your app/build.gradle file so that no dependency collisions are introduced. You can see the result of this step by running ./gradlew :app:dependencies.

Download and add the google-services.json file to your Android app module’s root folder.


Downloading and installing the Google Services JSON file

Step 6 – Integrating FCM into the Ably app

In this step, we’ll integrate FCM into our Ably app. Go ahead and add a new folder in the same directory as your MainActivity.java file, and name it ‘receivers’. In this new folder, add a new file and name it AblyPushMessagingService.java. In this file, we’ll add code to show a Push Notification on the device when it receives one. Paste the following into this file:

package YOUR-PACKAGE-NAME;

import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class AblyPushMessagingService extends FirebaseMessagingService {
    public static final String PUSH_NOTIFICATION_ACTION = AblyPushMessagingService.class.getName() + ".PUSH_NOTIFICATION_MESSAGE";

    @Override
    public void onMessageReceived(RemoteMessage message) {
        //FCM data is received here.

        Intent intent = new Intent(PUSH_NOTIFICATION_ACTION);
        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
    }

}

Next, go ahead and add this class as a service in your manifest fie. Open the AndroidManifest.xml and paste the following after <activity></activity> within the <application></application tags.

<service android:name=".receivers.AblyPushMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

Next, in the same folder that you created before, add a new file and name it AblyPushMessagingService.java and paste the following into it.

package YOUR-PACKAGE-NAME;

import io.ably.lib.push.AblyFirebaseInstanceIdService;

/**
 * Leave this empty as the base class AblyFirebaseInstanceIdService does the FCM token registration.
 * In case your app requires access to FCM token as well, then override io.ably.lib.push.AblyFirebaseInstanceIdService#onTokenRefresh()
 */
public class AblyPushRegistrationService extends AblyFirebaseInstanceIdService {
    @Override
    public void onTokenRefresh() {
        //Make sure to call super.onTokenRefresh to initialize Ably push environment.
        super.onTokenRefresh();
    }
}

Step 7 – Setting up a Node.js server for authentication

In this step, we’ll setup an authentication server that’ll authenticate the clients with Ably using the Token Authentication strategy.

When a client is authenticated and connected to Ably, they are considered to be an authenticated client. Whilst an authenticated client has a verifiable means to authenticate with Ably, they do not necessarily have an identity. When using the Token Authentication, you can have your sever assign the IDs to all the clients at the time of authentication. Unlike in Basic Authentication, you’ll not be using a direct API key on the client side. Instead, you’ll use an authUrl or authCallback to authenticate the clients. This is a recommended strategy to perform authentication on the client side as illustrated in our Best Practice Guide

We’ll use Node to implement the server. Install node.js if you do not have it by downloading it from here.

Next, create a folder and change to this directory. We’ll be using express.js in this tutorial. It’s a JavaScript framework that makes it easy to accept REST request and send responses back, without needing to worry about the protocol level details. More information on express.js can be found on the website.

Run the following command in terminal:

npm init

You will be prompted to provide some information, just hit return in order to accept the defaults and then run the following commands to install our dependencies:

npm install express --save
npm install ably --save

Create a file called ‘index.js’ and add the following code for authentication.

const express = require('express')
const Ably = require('Ably');
var realtime = new Ably.Realtime({key: "YOUR_API_KEY" });
const app = express()
const port = 3000
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

app.get('/auth', function (req, res) {
  var tokenParams = {
    'clientId': /* Assign a random id */
  }; 
  realtime.auth.createTokenRequest(tokenParams, function(err, tokenRequest) {
    if (err) {
      res.status(500).send('Error requesting token: ' + JSON.stringify(err));
    } else {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(tokenRequest));
    }
  });
});

You can return to your terminal and run your node server using the following command:

$ node index.js

If you navigate to http://localhost:3000/auth on your web browser, you should see the following response, indicating authentication was successful:

{
  "keyName": /* your key name */,
  "ttl": 3600000,
  "timestamp": 1473894038255,
  "capability": "{\"*\":[\"*\"]}",
  "nonce": "f6799a8e7fa6f77b8e1dac55314789b1",
  "mac": "35nifY9SRZ8KRDfKOPIS1qYWGP16r2lD59zJo9TH8pA="
}

The above is a trivial auth example as anybody accessing the auth endpoint is automatically given a token. If you would like more information on auth and on it’s implementation head over to the Token Authentication tutorial.

Step 8 – Directly registering the devices with FCM

In this step, we’ll activate (or register) the device with Firebase Cloud Messaging server, for the device to be able to receive the Push Notifications sent to it.

In your MainActivity.java file, define a new function called initAblyPush(), as follows:

private void initAblyPush() throws AblyException {
    ablyRealtime.push.activate();
}

In the above function, we’ve used the activate method to register the device directly with FCM.

Next, we’ll need to have the device subscribe to the channel on which the notifications will be sent. Add another function definition as shown below:

private void subscribeChannels() {
    ablyRealtime.channels.get(TEST_PUSH_CHANNEL_NAME).push.subscribeClientAsync(new CompletionListener() {
        @Override
        public void onSuccess() {
            logMessage("Subscribed to push for the channel " + TEST_PUSH_CHANNEL_NAME);
            handler.sendMessage(handler.obtainMessage(SUCCESS, STEP_4));
        }

        @Override
        public void onError(ErrorInfo reason) {
            logMessage("Error subscribing to push channel " + reason.message);
            logMessage("Visit link for more details: " + reason.href);
            handler.sendMessage(handler.obtainMessage(FAILURE));
        }
    });

}

Step 9 – Testing the app

Now that our app is ready, let’s test it out. Before using any of these methods make sure you have built and run the app on a real Android device and accepted push notifications.

a. Testing using Device ID

Add the following method to your index.js file and replace “YOUR_DEVICE_ID” with the id you just retrieved from your console:

app.get('/push/device', function (req, res) {
  var recipient = {
    deviceId: 'YOUR_DEVICE_ID'
  };
  var notification = {
    notification: {
      title: 'Hello from Ably!'
    }
  };
  realtime.push.publish(recipient, notification, function(err) {
    if (err) {
      console.log('Unable to publish push notification; err = ' + err.message);
      return;
    }
    console.log('Push notification published');
    res.send("Push Sent");
  });
})

Restart the server and navigate to YOUR-SERVER-URL/push/device in your browser. You should be able to receive a notification on your device as shown the image below.


Android Push Notification Screenshot

b. Testing using Client ID

We can test sending push notifications using the client ID. This allows us to push all devices with that client ID by adding the following code to your index.js file:

app.get('/push/client', function (req, res) {
  var recipient = {
    clientId: 'iosPushClient'
  };
  var notification = {
    notification: {
      title: 'Hello from Ably!'
    }
  };
  realtime.push.publish(recipient, notification, function(err) {
    if (err) {
      console.log('Unable to publish push notification; err = ' + err.message);
      return;
    }
    console.log('Push notification published');
    res.send("Push Sent");
  });
})

Note: If your clientId is different make sure to replace the clientId specified in the recipient object above with that.

Restart the server and go to YOUR-SERVER-URL/push/client you should receive a notification on your device as shown in the image below.


Android Push Notification Screenshot

c. Testing using Channels

Channel based Push Notifications allow us to push messages to a channel that the client on the device has subscribed to. To test this, add the following code to our index.js file:

app.get('/push/channel', function (req, res) {
  var extras = {
    push: {
      notification: {
        title: 'Hello from Ably!',
        body: 'Example push notification from Ably.'
      },
      data: {
        foo: 'bar',
        baz: 'qux'
      }
    }
  };

  var channel = realtime.channels.get('push');
  channel.publish({ name: 'example', data: 'data', extras: extras }, function(err) {
    if (err) {
      console.log('Unable to publish message with push notification; err = ' + err.message);
      return;
    }
    console.log('Message with push notification published');
    res.send("Push Sent");
  });
})

Restart the server and go to YOUR-SERVER-URL/push/channel. You should receive a notification on your device. Notice how this notification has both a title and a body. You can do this on all three types of push notifications mentioned earlier, if you wish. Simply add a body object to the notification part of your push payload. See the above code for an example of this.


Android Push Notification Screenshot

Further Reading

  • FCM documentation is a good place to find more information on the use of Push Notifications in Android.
  • You may also wish to deregister devices or get all of the stats related to your application implementing Push Notifications, such as the number and type of device registrations, etc. More information on how to do this can be found in the Push Admin API docs.

Ready to get started?

Our free plan includes 3m messages per month, 100 peak connections, 100 peak channels, and loads of features.