Using the Android App Messaging SDK
A tutorial for adding the Android App Messaging SDK code to your app to allow your app users to receive push notifications from your Dotdigital account
Our Android App Messaging SDK uses Firebase Cloud Messaging (FCM) to send push notifications to your Android app users. If you haven't already configured a Firebase project then please follow the instructions here first.
Our Android SDK is open source and can be found on Github here:
Basic sample apps can be found in Github here:
To embed in your native Android apps, complete the following tasks:
- Install the SDK
- Configure the SDK
- Initialise the SDK
- Handle push message callbacks
- Optional: Change the icon or colour of your push notifications
- Starting a session
Handling asynchronous requests
You can use either standard callbacks or observable streams to handle asynchronous requests. If you want to use observable streams, you need to add the RxJava library to your app. This tutorial explains how to use standard callbacks, but you can find a full sample of the RxJava code in the 'Android sample code' section.
Installing the SDK
- Add the App Messaging SDK to your module-level
build.gradle
file and change theminSDKVersion
attribute to 21 using the snippet below:
defaultConfig {
...
minSdkVersion 21
...
}
Dependencies {
...
implementation 'com.comapi:foundation:1.5.0'
}
Seeing a 'Manifest merger' error?
The SDK has a
minSDKVersion
attribute of 21, your attribute must have a value of at least 21.
- Add the following dependencies to your top level build.gradle file:
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.3.10'
}
}
- Add the following plugins to your module level build.gradle file:
plugins {
id 'com.google.gms.google-services'
}
- Copy the google-services.json file from your Firebase project into your apps root folder; this is used to configure your app to work with Firebase.
Sync Now
Remember to synchronise
build.gradle
files after making any changes.
Configuring the Android SDK
Before you can configure the Android SDK, you need the following:
- The value of the API space ID field in Dotdigital
- A class that creates a JWT token
- Create a placeholder push handler class which implements the
PushMessageListener
interface to facilitate callbacks. The PushHandler is the class we passed instance of into the SDK init method. Inside we will get push messages delivered when the app is visible.
public class PushHandler implements PushMessageListener {
@Override
public void onMessageReceived(RemoteMessage message) {
// TODO : Add push message handling such as displaying messages when in foreground and
// handling deep links and custom data
}
- Create a new instance of the
ComapiConfig
class, pass your API Space Id to theapiSpaceId()
method, and an instance of the class that creates a JWT to theauthenticator()
method using the snippet below:
ComapiConfig config = new ComapiConfig()
// Set the id of the app space, the device belongs to
.apiSpaceId("<API_SPACE_ID>")
// Sets handler for authentication challenges (SDK asking for JWT token)
.authenticator(new ChallengeHandler())
// When we are initialising the SDK in the Application class we will need to provide pushMessageListener to listen for push notification delivered when the app is visible to the user.
// In which case Android won't display notification in the system tray.
.pushMessageListener(new PushHandler());
Configuring logs and proxy servers
You can set internal file logs, console logs, and network logs to the following levels. By default all log levels are set to WARNING
:
OFF
FATAL
ERROR
WARNING
INFO
DEBUG
To change the log levels, add the following line to the ComapiConfig
object and use the appropriate method on the LogConfig
object:
.logConfig(new LogConfig().setFileLevel(LogLevel.DEBUG).setConsoleLevel(LogLevel.INFO).setNetworkLevel(LogLevel.ERROR));
You can also set a custom limit for the size of internal log files by calling the following method on the ComapiConfig
object:
.logSizeLimitKilobytes(limit);
If your app connects through a proxy server, add the following line to the ComapiConfig
object:
.apiConfiguration(new APIConfig().proxy("http://xx.xxx.xx.xxx:xxxx"));
Initialising the Android SDK
Where to initialise the Android SDK in the activity lifecycle
Initialisation needs to be performed in the
onCreate()
of the AndroidApplication
class so that your app users' profiles don't change every time they open the app. Remember to set that class in the manifest file<application android:name=".MyApplicationClass">
- After you've configured the SDK, import the
com.google.firebase.FirebaseApp
class into your java file, and initialise the Firebase SDK.
FirebaseApp.initializeApp(this);
Firebase must be initialised before the SDK
Please ensure the
FirebaseApp.initializeApp(this);
is called prior to initialising the SDK as it is a dependency of the SDK.
2 Pass the ComapiConfig
object to one of the following initialisation methods. These methods return a ComapiClient
object that you can use to access the session
and profile
services, which are used to create and update the user's profile.
- The
initialiseShared()
method: When you use this method, the SDK stores the 'ComapiClient' object, and you can access it at any time by calling theComapi.getShared()
method (Java) or theRxComapi.getShared()
method (RxJava) - The
initialise()
method: When you use this method, you need to store theComapiClient
object yourself. ThegetShared()
method is not available when you use this method.
public class MyApplication extends Application implements Callback<ComapiClient> {
@Override
public void onCreate()
{
super.onCreate();
FirebaseApp.initializeApp(this);
ComapiConfig config = new ComapiConfig()
// Set the id of the app space, the device belongs to
.apiSpaceId("<API_SPACE_ID>")
// Sets handler for authentication challenges (SDK asking for JWT token)
.authenticator(new ChallengeHandler())
// When we are initialising the SDK in the Application class we will need to provide pushMessageListener to listen for push notification delivered when the app is visible to the user.
// In which case Android won't display notification in the system tray.
.pushMessageListener(new PushHandler());
// Asynchronously initialise Foundation SDK client (retrieve it in callback).
// Shared makes ComapiClient accessible from Comapi.getShared()
// The last this passed requires your class to implement Callback<ComapiClient> which will be invoked when the SDK has initialised
Comapi.initialiseShared(this, config, this);
}
// This is a callback for successful initialisation. We need to send a message to Activities
// that SDK is initialised and the client is available.
@Override
public void success(ComapiClient client) {
// SDK initialised
}
@Override
public void error(Throwable t) {
// Error when initialising SDK
}
}
Handle push message callbacks
It is now time to flesh out the functionality of the push handler class which is called whenever the SDK receives a push message. Add the functionality as guided below:
Push handler callback
Within your push handler class that implements the pushMessageListener
interface you'll need to decide what you want to do with the push notification and implement the logic by overriding the onMessageReceived()
method. This method is called when a push notification is received while the app is in the foreground.
Functionality may include:
Displaying push notifications when the app is in the foreground
Push notifications are automatically displayed by Android only while the app is in the background. These notifications are sent to the system tray and launch your app when users tap them, but if you want to display the push notification message while the app is in the foregrounded, you will need to implement this.
You can extract the received push message body by calling message.getNotification().getBody()
within your onMessageReceived
handler.
Please see the official Android guidance for notfications to understand how to implement this, the example provided below is a basic implementation for illustrative purposes:
- Within your method you can retrieve the push message body by calling
message.getNotification().getBody()
you can then display or store this as desired e.g.
public class PushHandler implements PushMessageListener {
@Override
public void onMessageReceived(RemoteMessage message) {
try {
PushDetails result = ComapiClient.parsePushMessage(message);
} catch (JSONException e) {
e.printStackTrace();
}
// Create an Intent for the activity you want to start
Intent resultIntent = new Intent(context, MainActivity.class);
// Create the TaskStackBuilder and add the intent, which inflates the back stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntentWithParentStack(resultIntent);
// Get the PendingIntent containing the entire back stack
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
// Check if message contains a notification payload.
if (message.getNotification() != null) {
// Do something with the message to notify the user
// Example
createNotificationChannel(context);
Notification.Builder b = new Notification.Builder(context);
b.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("foreground message")
.setContentText(message.getNotification().getBody())
.setContentInfo("INFO")
.setContentIntent(resultPendingIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
b.setChannelId("test_channel_id1");
}
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(1, b.build());
}
}
private void createNotificationChannel(Context context) {
// Create the NotificationChannel, but only on devices that have the Google API 26+ because the NotificationChannel class is new and not in supported in older libraries
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "test channel";
String description = "some description";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("test_channel_id1", name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
Handling deep links
The SDK is capable of handling deep links for you and tracking their usage, which is our recommendation.
If you want to trigger deep links within your own app to drive functionality then you will need to implement these in your app prior to sending them in a push, however you can always use a URL or invoke another apps deep links without needing to implement your own deep link schema.
To enable deep link handling you must do the following:
- For the app to understand which Activity it should open when myappscheme://mycustomhost link is invoked the activity declaration in AndroidManifest should have an intent-filter:
<activity
android:name=".MyActivity"
android:exported="true">
<intent-filter android:label="Open Activity in DotDigital Sample App">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with "myappscheme://mycustomhost” -->
<data
android:host="mycustomhost"
android:scheme="myappscheme" />
</intent-filter>
</activity>
- Your apps MainActivity needs to be adapted to ensure that any intents passed (onCreate, onNewIntent) to the app are checked to see if they are push messages so that the SDK can automatically track the usage in Dotdigital's analytics and invoke any deep links that are associated with the push action. In addition you app will need to request permission to invoke deep links in later versions of Android, so this needs to be down in the onCreate. The code required is shown below:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
askPermission();
ComapiClient client = Comapi.getShared();
handlePush(client, getIntent());
}
// If you use android:launchMode="singleTop" this will be the place where new intent for an exsiting activity will be delivered
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
ComapiClient client = Comapi.getShared();
handlePush(client, intent);
}
// Starting from Android 13 (API level 33) we need a runtime permission to post notifications.
// SDK manifest already includes POST_NOTIFICATIONS permission, but we also need to trigger a popup with permission request.
// This is done with askPermission() method. You should handle the response from the popup as we have not shown this in this basic example.
void askPermission() {
if (Build.VERSION.SDK_INT >= 33) {
if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// Manifest.permission.POST_NOTIFICATIONS is available only if 'compileSdk 33' or greater
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS},
1);
}
}
}
// Handle push notification intents
void handlePush(ComapiClient client, Intent intent) {
if (client != null) {
// If we call client.handlePushMessage method providing the Activity context, intent that started the Activity, a boolean flag telling the SDK if it should open a deep link if it finds it and a callback
// then the sdk will track the notification click interaction in dotdigital analitics, optionally try to start activity open the deep link and as a result will provide an object with:
// getUrl - dotdigital deep link url
// getData - dotdigital custom data
// isTrackingSuccessful - was the click recorded in analytics
// isStartActivitySuccessful - was the activity associated with the deep link started
client.handlePushNotification(this, intent, true, new Callback<PushHandleResult>() {
@Override
public void success(PushHandleResult result) {
}
@Override
public void error(Throwable t) {
}
});
}
}
}
Extracting a deep link
If you wish to extract a deep link URL so that you can choose how to handle it, you can use the parsePushMessage function to parse Dotdigital data for the URL or Firebase RemoteMessage (a helper method).
public class PushHandler implements PushMessageListener {
@Override
public void onMessageReceived(RemoteMessage message) {
try {
// Let the SDK parse the push payload
PushDetails result = ComapiClient.parsePushMessage(message);
// Get the deep link URL if available
Log.i("TEST", "url = " + result.getUrl());
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Handling custom data payloads
The SDK can deliver custom data payloads via the push messages which your app can interpret to drive functionality. To access this custom data you will need to call the SDKs parsePushMessage()
method with the contents of the push message. Any custom data can be extracted for the returned object by calling getData()
. Add this code to your onMessageRecevied()
implementation in your push handler class. e.g.
public class PushHandler implements PushMessageListener {
@Override
public void onMessageReceived(RemoteMessage message) {
try {
// Let the SDK parse the push payload
try {
PushDetails result = ComapiClient.parsePushMessage(message);
} catch (JSONException e) {
e.printStackTrace();
}
// Get the custom data using getData()
Log.i("TEST", "data = " + (result.getData() != null ? result.getData().toString() : "null"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
Changing the icon or colour of push notification
Our Android SDK uses Firebase Cloud Messaging (FCM) to send push notifications to your Android app users. As such, you can use the Android <meta-data>
tag in your AndroidManifest.xml
file to change the icon and colour of your push notifications.
<!-- Set icon used with incoming notification messages. This is used when no icon is set for the incoming notification message. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher_round" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming notification message. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/colorAccent" />
Sessions
Starting a session
To receive push messages the SDK requires an active session.
To create a session you must have successfully initialized and retrieved a client object, you need to be able to identify the app user so that the sub claim in the JWT can be populated for the user; when the SDK requests the JWT in order to start the session. To create a session call:
client.service().session().startSession(new Callback<Session>(){/* implement */});
rxClient.service().session().startSession()
.subscribe(new Observer<Session>(){/* implement */});
Ending a session
You only have to end a session if you want to stop the app receiving push notifications, or you want to change users on the app.
To end the current session, call:
client.service().session().endSession(
new Callback<ComapiResult<Void>>(){/* implement */});
rxClient.service().session().endSession()
.subscribe(new Observer<ComapiResult<Void>>(){/* implement */});
Next steps
Now ensure your app passes an email address to the SDK for the app user to ensure they get a contact created in Dotdigital by following these instructions
Want to know more about the SDK?
To find out more about the SDK and it features and functions please go here
Updated about 2 years ago