Introduction

The Kumulos SDK is an open source project hosted on Github and can be found at https://github.com/Kumulos/KumulosSdkAndroid.

This guide assumes you have completed the steps from the introduction and have configured your Firebase Console and Unica App with the relevant credentials for Cloud Messaging. It will cover the following steps:

  1. Integrate the SDK and configuring your project

  2. Initializing the SDK components within your project and register for push notifications

  3. Register the Kumulos InstallationID with your backend to provide a link between the device and your users represented in your CRM backend for later targeting of notifications.

  4. Sending a test push notification from your Unica app and receiving on the device.

  5. Custom analytics events

  6. Optional advanced behavior for native push notifications

  7. Optional advanced behavior for rich In-App messages

This Kumulos SDK can be used with Android apps developed in Java and Kotlin.

Integration

Project setup

Add the Firebase components to your app as shown below.

In the root build.gradle, ensure the Google repository is enabled and the google-services plugin is on the classpath:

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

allprojects {
    // ...
    repositories {
        google() // Google's Maven repository
        // ...
    }
}

The Kumulos libraries are distributed through JCenter. To install the libraries, edit your app's build.gradle file to add the following:

  • Exclude conflicting meta data files from the build
  • Declare source & target compile options
  • Add the Kumulos library dependencies
  • Add the Firebase core SDK
  • Apply the google-services plugin

A sample build.gradle can be seen below.

android {
    // Exclude duplicate files from the build
    packagingOptions {
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/ASL2.0'
        exclude 'META-INF/LICENSE'
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

apply plugin: 'com.android.application'

dependencies {
    // Kumulos debug & release libraries
    debugImplementation 'com.kumulos.android:kumulos-android-debug:11.5.0'
    releaseImplementation 'com.kumulos.android:kumulos-android-release:11.5.0'
    implementation 'com.google.firebase:firebase-core:16.0.7'
}

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

The debugImplementation will have logging enabled with a tag matching com.kumulos.*. When running in debug mode, the log messages should be visible in LogCat.

Run a Gradle sync to install the Kumulos libraries and build your project.

By default, the Firebase SDK will send analytics data to Google. To disable this, simply add <meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" /> to your app's AndroidManifest.xml

Download the google-services.json file from your Firebase app 'General' settings, and add it to your app/ folder.

Now you can add the Kumulos FirebaseMessagingService and PushBroadcastReceiver to your AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">

    <!-- Optionally add the wake lock permission to stop the CPU from sleeping when a message is received -->
    <!-- <uses-permission android:name="android.permission.WAKE_LOCK" /> -->
    <!-- Optionally add the boot completed permission to allow periodic tasks to persist across phone reboots -->
    <!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->

   <!-- Set the android:name to your custom Application class -->
    <application
        android:name=".ExampleApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
        </application>

        ...

        <!-- Kumulos FCM handler -->
        <service android:name="com.kumulos.android.FirebaseMessagingService" android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

        <!-- Kumulos Push receiver -->
        <receiver android:name="com.kumulos.android.PushBroadcastReceiver" android:exported="false">
            <intent-filter>
                <action android:name="com.kumulos.push.RECEIVED" />
                <action android:name="com.kumulos.push.OPENED" />
                <action android:name="com.kumulos.push.DISMISSED" />
                <action android:name="com.kumulos.push.BUTTON_CLICKED" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

Initialization

To initialize the SDK, we recommend subclassing the Application class and initializing Kumulos in its onCreate method.

package com.example;

import android.app.Application;
import com.kumulos.android.Kumulos;
import com.kumulos.android.KumulosConfig;

public class ExampleApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        KumulosConfig config = new KumulosConfig.Builder("YOUR_API_KEY", "YOUR_SECRET_KEY")
            .enableInAppMessaging(KumulosConfig.InAppConsentStrategy.AUTO_ENROLL)
            .build();

        Kumulos.initialize(this, config);

        Kumulos.pushRegister(context);
    }
}

If you want to unregister the installation, you can use Kumulos.pushUnregister(context).

Registering with your CRM

Installation ID

When initialized for the first time, the Kumulos SDK will create a unique identifier for the app installation that initialized the SDK, this identifier can be used later to target push notifications to a specific device.

In order to retrieve this installation ID, simply access the class variable:

String id = com.kumulos.android.Installation.id(context);

Once you have the installation ID, you can send it to your app's CRM backend to be used later for push targeting.

User Association

If your app makes use of an identifier to uniquely tell which user is signed into a device (for example a primary key integer or UUID, or an email address), you can send this identifier to Kumulos for later push targeting via the same key.

Kumulos.associateUserWithInstall(context, "unique-user-identifier");

Sending a test

TODO: Unica system / screenshots.

Event Tracking

Kumulos allows you to track custom analytics events in order to observe your users activity in your app, allowing you to analyse behavior and optimize journeys to ensure your users are getting the full benefit of your app's features.

To track a custom analytics event, use Kumulos.trackEvent as follows:

JSONObject props = new JSONObject();
props.put("productId", 404);
Kumulos.trackEvent(context, "product.purchased", props);

Each event and its properties must be less than 250 KiB in size for the event to be tracked.

Event tracking is available offline as all events are persisted locally before being synced to the server in batches in the background.

A similar method trackEventImmediately will immediately start an event sync rather than waiting for the next time the app is backgrounded.

Push Notification Advanced features

Handling Push Action Buttons

Push messages allow you to hand-off to native application screens via deep-linking push action buttons. When tapped, these buttons pass control to the defined push action handler.

If you want to handle deep-links as part of a Push message you can create a class that implements the PushActionHandlerInterface and assign it during SDK initialization.

Kumulos.setPushActionHandler(new MyPushActionHandler());

A stub implementation of the handler could be as follows:

public class MyPushActionHandler implements PushActionHandlerInterface {
    public void handle(Context context, PushMessage pushMessage, String actionId){
       //- actionId is the button id you set when creating the notification
       //- Note, that when action button is clicked your app's activity is not launched -- you have to do it yourself in this handler.
    }
}

Default Push Behavior

By default, the Kumulos PushBroadcastReceiver will show a notification on the notification area of the device when a content push is received.

Tapping this notification will open the main launcher activity of your application and track the push open conversion for you.

Your main activity will receive the push contents in its options bundle under the PushMessage.EXTRAS_KEY.

Changing the Push Icon

To change the icon shown in the status bar on Android, you can configure Kumulos with a drawable at initialization time:

KumulosConfig config = new KumulosConfig.Builder("API_KEY", "SECRET_KEY")
    .setPushSmallIconId(R.id.my_push_small_icon)
    .build();
Kumulos.initialize(this, config);

Make sure to comply with the status bar icon guidelines so the icon renders correctly on all devices. For help preparing assets, we suggest checking out the Android Asset Studio

Customizing Push Behavior

To customize the behavior of the SDK when a push is received or its notification is tapped, we suggest subclassing the PushBroadcastReceiver and overriding its base methods depending on what you want to customize.

Example extension class:

package com.example;

import com.kumulos.android.PushBroadcastReceiver;

public class MyPushReceiver extends PushBroadcastReceiver {

}

Make sure to change the AndroidManifest.xml receiver:

<receiver android:name="com.example.MyPushReceiver" android:exported="false">
    <intent-filter>
        <action android:name="com.kumulos.push.RECEIVED" />
        <action android:name="com.kumulos.push.OPENED" />
        <action android:name="com.kumulos.push.DISMISSED" />
        <action android:name="com.kumulos.push.BUTTON_CLICKED" />
    </intent-filter>
</receiver>

Changing the Launched Activity

To change which activity will be launched when the user taps a notification, you can override the PushBroadcastReceiver#getPushOpenActivityIntent(Context, PushMessage).

package com.example;

import android.content.Context;
import android.content.Intent;

import com.kumulos.android.PushBroadcastReceiver;
import com.kumulos.android.PushMessage;

public class MyPushReceiver extends PushBroadcastReceiver {

    @Override
    protected Intent getPushOpenActivityIntent(Context context, PushMessage pushMessage) {
        // TODO implement your own logic here
        return super.getPushOpenActivityIntent(context, pushMessage);
    }
}

The PushMessage model will not be added to the Intent by default, it is up to you to add it as an extra if desired:

Intent launchIntent = new Intent(context, MyActivity.class);
launchIntent.putExtra(PushMessage.EXTRAS_KEY, pushMessage);

You can return null to track the push conversion and do nothing when the notification is tapped.

If the Intent returned does not describe an Activity, it will be ignored

Customizing the Notification

To customize the notification shown to the user for content pushes, you can override PushBroadcastReceiver#buildNotification(Context, PushMessage).

package com.example;

import android.app.Notification;
import android.content.Context;

import com.kumulos.android.PushBroadcastReceiver;
import com.kumulos.android.PushMessage;

public class MyPushReceiver extends PushBroadcastReceiver {

    @Override
    protected Notification buildNotification(Context context, PushMessage pushMessage) {
        // TODO customize the notification
        return super.buildNotification(context, pushMessage);
    }
}

If you want to handle the open/dismissal with the broadcast receiver, be sure to set up the content intents of the notification as follows:

Intent openIntent = new Intent(PushBroadcastReceiver.ACTION_PUSH_OPENED);
        openIntent.putExtra(PushMessage.EXTRAS_KEY, pushMessage);
        openIntent.setPackage(context.getPackageName());

PendingIntent pendingOpenIntent = PendingIntent.getBroadcast(
        context,
        pushMessage.getId(),
        openIntent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);

...

notificationBuilder.setContentIntent(pendingOpenIntent);

//Similarly
Intent dismissedIntent = new Intent(PushBroadcastReceiver.ACTION_PUSH_DISMISSED);

dismissedIntent.putExtra(PushMessage.EXTRAS_KEY, pushMessage);
dismissedIntent.setPackage(context.getPackageName());

PendingIntent pendingDismissedIntent = PendingIntent.getBroadcast(
        context,
        pushMessage.getId(),
        dismissedIntent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);

...

notificationBuilder.setDeleteIntent(pendingDismissedIntent);

This will ensure that the notification conversion is tracked in Kumulos.

If you want to do something else, you can manually track push open conversion using Kumulos#pushTrackOpen(Context, int) and track dismissed event using Kumulos#pushTrackDismissed(Context, int). Additionally, you would have to add deep link extras for in-app message deep links to keep working.

Kumulos.pushTrackOpen(context, pushMessage.getId());
Kumulos.pushTrackDismissed(context, pushMessage.getId());
//call in the scope of MyPushReceiver
addDeepLinkExtras(pushMessage, launchIntent);

Launching a Service for Background Data Pushes

To launch a service when a background data push is received, you can override PushBroadcastReceiver#getBackgroundPushServiceIntent.

package com.example;

import android.content.Context;
import android.content.Intent;

import com.kumulos.android.PushBroadcastReceiver;
import com.kumulos.android.PushMessage;

public class MyPushReceiver extends PushBroadcastReceiver {

    @Override
    protected Intent getBackgroundPushServiceIntent(Context context, PushMessage pushMessage) {
        // TODO implement your own logic here
        return super.getBackgroundPushServiceIntent(context, pushMessage);
    }
}

This would easily allow you to handle data processing in the background by launching an IntentService for example.

The PushMessage model will not be added to the Intent by default, it is up to you to add it as an extra if desired:

Intent serviceIntent = new Intent(context, MyIntentService.class);
serviceIntent.putExtra(PushMessage.EXTRAS_KEY, pushMessage);

Return null if you want to do nothing with the data push.

If the Intent returned does not describe a Service, it will be ignored

URL Pushes

Push notifications sent to open a URL will, by default, track the push open if the user taps the notification, and then open the default web browser.

Overriding All Behaviors

If you want to completely replace the logic for handling pushes, you can override PushBroadcastReceiver#onPushReceived(Context, PushMessage).

Bear in mind you will be responsible for all aspects of the push process such as showing a notification to the user, tracking an open conversion using Kumulos#pushTrackOpen(Context, int) and dismissed events using Kumulos#pushTrackDismissed(Context, int), or launching any activities or services.

In addition, you may need to implement behaviors for:

  • Delivery tracking: pushTrackDelivered(context, pushMessage)

Using Your Own FirebaseMessagingService with Kumulos

If you already consume FCM push notifications with your own FirebaseMessagingService but you want to also enjoy the benefits of the Kumulos push service, you can use the SDK's helper methods in your own implementation. For example:

public class MyAppFirebaseMessagingService extends com.google.firebase.messaging.FirebaseMessagingService {

    @Override
    public void onNewToken(String token) {
        // Handle token for your purposes
        // ...
        // Also pass token to Kumulos for registration
        Kumulos.pushTokenStore(this, token);
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // Handle message as you wish
        // ...
        // Hand over to Kumulos if not of interest / came from the Kumulos push service
        com.kumulos.android.FirebaseMessageHandler.onMessageReceived(this, remoteMessage);
    }
}

Configuring HCM for Huawei Devices

The latest Huawei handsets such as the P40 family of phones do not use FCM for push notifications and instead use Huawei Cloud Messaging (HCM). If you want to message all of your Android users, including those using Huawei phones, then in addition to FCM you also need to configure HCM as described in this section. After completing the above FCM configuration, you also need to:

  1. Set up an app on the Huawei Mobile Developer Console
  2. Configure Push for your Unica App
  3. Add Huawei Mobile Services dependencies, files, and plugins to your project
  4. Add Huawei-specific manifest entries

All other push behavior & customization works identically to FCM.

To begin, configure a project on the Huawei Developer Console, and enable the Push feature by following the Push Kit developer guide step 1.

Note that your app needs a valid signing configuration for both debug and release builds. The SHA-256 fingerprint of your signing key is required to match the Huawei app configuration prior to continuing.

Once you have created the app, configure the HCM gateway for your Unica App. You need to enter the App ID and App Secret from the Huawei Developer Console.

HMS AppGallery Connect Credentials

Once configured, now add the agconnect-services.json file (downloaded from the Huawei developer console) to your Android application directory.

Next, add the Huawei repository and plugin dependency to your root build.gradle:

buildscript {
    repositories {
        ...
        maven {
            url 'https://developer.huawei.com/repo/'
        }
    }
    dependencies {
        ...
        classpath 'com.huawei.agconnect:agcp:1.2.1.301'
    }
}

allprojects {
    repositories {
        ...
        maven {
            url 'http://developer.huawei.com/repo/'
        }
    }
}

Next, in your app-level build.gradle, add the HMS Push Kit dependency and apply the HMS plugin:

dependencies {
    ...
    implementation 'com.huawei.hms:push:4.0.3.300'
}

// Add to bottom of file
apply plugin: 'com.huawei.agconnect'

Finally, add the HMS push service to your AndroidManifest.xml:

<!-- Kumulos HMS Messaging Service -->
<service
    android:name="com.kumulos.android.HmsMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.huawei.push.action.MESSAGING_EVENT" />
    </intent-filter>
</service>

Push registration should be performed as with FCM.

Huawei Troubleshooting

  • Whilst the Kumulos SDK minSdk is 16, when you use the HMS Push Kit, the minSdk will need to be 17 for your app
  • Ensure the app signing key fingerprint matches in the Huawei developer console
  • Ensure the app package matches the configuration in the Huawei developer console
  • Ensure the Push Kit service is enabled for your app in AppGallery Connect > App > Develop > Grow > Push Kit. You must enable the service and choose a data storage location.

In-App Advanced Features

If you would like your users to opt-in to receive In-App messages you can configure the SDK during initialization to make opt-in explicit by setting the strategy, then calling the SDK helper to manage their consent.

// Set the strategy to require explicit user consent
KumulosConfig config = new KumulosConfig.Builder("YOUR_API_KEY", "YOUR_SECRET_KEY")
    .enableInAppMessaging(KumulosConfig.InAppConsentStrategy.EXPLICIT_BY_USER);
    .build();

// Call this method to update consent based on user preferences / settings screen etc.
KumulosInApp.updateConsentForUser(true);

Deep-linking for In-App

In-App messages allow you to hand-off to native application screens via deep-linking action buttons. When tapped, these buttons pass control to the defined deep-link handler, including their defined data payload (configured in the In-App message composer for the action button).

If you want to handle deep-links with custom data payloads as part of an In-App message you can create a class that implements the InAppDeepLinkHandlerInterface and add it to your configuration options during SDK initialization:

KumulosInApp.setDeepLinkHandler((context, buttonPress) -> {
    JSONObject deepLink = buttonPress.getDeepLinkData();
    JSONObject messageData = buttonPress.getMessageData();

    // TODO: Inspect the deep link & message data to perform relevant action
});

Using the In-App Inbox

In-app messages can optionally be persisted in a user-level inbox for later retrieval. This allows you to build features such as loyalty rewards or expiring coupons into your app. Regardless of whether they are stored in the inbox, the maximum amount of in-apps stored on a device is 50 (the oldest messages exceeding this limit will be evicted).

Retrieve messages

To retrieve a list of messages from the user's inbox and present the first in the list, see the following example:

List<InAppInboxItem> items = KumulosInApp.getInboxItems(context);
KumulosInApp.presentInboxMessage(context, items.get(0));

Mark as read

To mark a single or all inbox messages as read:

//single
List<InAppInboxItem> items = KumulosInApp.getInboxItems(context);
KumulosInApp.markAsRead(context, items.get(0));

//all
KumulosInApp.markAllInboxItemsAsRead(context);

Delete message

You can also delete an in-app message from inbox:

List<InAppInboxItem> items = KumulosInApp.getInboxItems(context);
KumulosInApp.deleteMessageFromInbox(context, items.get(0));

Inbox updated handler

In order to be notified when inbox changes you may set up a handler. The handler fires on the UI thread when one of the following happens to an in-app with an inbox configuration:

  • message fetched from server
  • message opened
  • message marked as read
  • message deleted
  • message evicted (expires or limit of stored messages exceeded)

You can use it as follows:

KumulosInApp.setOnInboxUpdated(() -> {
    List<InAppInboxItem> items = KumulosInApp.getInboxItems(context);

    //refresh your inbox
});

Note, you can do KumulosInApp.setOnInboxUpdated(null) when you stop being interested in inbox updates.

Get inbox summary

You can retrieve an inbox summary as follows:

KumulosInApp.getInboxSummaryAsync(context, (InAppInboxSummaryInfo summary) -> {
    if (summary != null){
        summary.getTotalCount();
        summary.getUnreadCount();
    }
});

The method runs asynchronously and calls back on the UI thread.

Get inbox item's image URL

Each inbox item may have an image associated with it. getImageUrl returns a URL to the image of specified width or null if there is no image.

List<InAppInboxItem> items = KumulosInApp.getInboxItems(context);

URL u = items.get(0).getImageUrl();

Troubleshooting

Proguard

If you are using ProGuard to optimize your Java code, then you need to ensure that your proguard.cfg file includes the Kumulos SDK and required components, something like:

-keep class com.google.android.gms.** { *; }
-dontwarn com.google.android.gms.
-keep class com.google.firebase.** { *; }
-dontwarn com.google.firebase.
-keep class android.support.v7.widget.** { *; }
-dontwarn android.support.v7.widget.
-keep class android.support.v4.widget.Space { *; }
-dontwarn android.support.v4.widget.Space
-keep class com.kumulos.** { *; }
-dontwarn com.kumulos.**
-keep class okhttp3.** { *;}
-dontwarn okhttp3.**
-keep class oikio.** { *;}
-dontwarn okio.**

Proguard is also very sensitive to UTF-8 with BOM encoding, whereas Android tooling will only accept UTF-8. Therefore, an easy way to ensure you don't inadvertently have UTF-8 byte-order-marks in your proguard.cfg is to use vim via the terminal as follows:

$ vim proguard.cfg
:set nobomb
:wq!

Changelog

1.1.0

  • Add In-App messaging to configuration
  • Add analytics event tracking
  • Break out advanced features into In-App and Push Notification headings, add In-App advanced config and Inbox features.
  • Standardize headings

1.0.0

  • Initial integration guide