Android SDK (19.02)


This document will guide you through the process of Countly SDK installation.

Minimum Android version

Countly Android SDK needs a minimum of Android 2.3+ (API Level 9).

Adding Countly SDK

You can use both Android Studio and Eclipse to add Countly SDK to your project.

Adding via Android Studio

To download the SDK package you can use the default JCenter repository. If it's not included in your project, include it like this:

buildscript {
    repositories {

You can also add the Bintray Maven repository:

buildscript {
    repositories {
        maven {
            url  ""

Now, add Countly SDK dependency (use latest SDK version from gradle, not the one below).

dependencies {
		compile ''

Adding SDK via Eclipse

Eclipse users can download JARs - sdk.jar or sdk.jar & sdk-messaging.jar depending on whether you need Countly Push support or not from Github Releases page.

Another option for Eclipse users is to use sources instead of jars. To do that, just create 2 or 3 packages and put sources from Github repository into corresponding packages.

Generate personalized SDK code snippets

Countly Code Generator can be used to generate SDK custom code snippets easily and fast. You can provide values for your custom event, or user profile, or just start with basic integration and this service will generate necessary code for you to use in your favorite IDE (e.g Android Studio).

Component Package name Path at Github repo
Countly SDK countly-sdk-android/sdk/src/main/java/ly/count/android/sdk
OpenUDID org.openudid countly-sdk-android/sdk/src/main/java/org/openudid
Countly Push SDK countly-sdk-android/sdk/src/main/java/ly/count/android/sdk/messaging

Setting up Countly SDK

First, you'll need to decide which device ID generation strategy to use. There are several options defined below:

First and easiest method is, if you want Countly SDK to take care of device ID seamlessly, use line below. Do not put a trailing "/" at the end of the server URL or it won't work. Starting from the version 16.12.03, the SDK will erase the trailing "/" and write a warning to the log.

Countly.sharedInstance().init(this, "https://YOUR_SERVER", "YOUR_APP_KEY")

Which server/host should I use inside SDK?

If you are using Countly Enterprise trial servers use, or Basically the domain you are accessing your trial dashboard from.

If you use Countly Lite and Countly Enterprise, use your own domain name or IP address like or https://IP (if SSL is setup).

Second, you can specify device ID by yourself if you have one (it has to be unique per device):

Countly.sharedInstance().init(this, "https://YOUR_SERVER", "YOUR_APP_KEY", "YOUR_DEVICE_ID")

Third, you can rely on Google Advertising ID for device ID generation.

Countly.sharedInstance().init(this, "https://YOUR_SERVER", "YOUR_APP_KEY", null, DeviceId.Type.ADVERTISING_ID)

Or, you can use OpenUDID:

Countly.sharedInstance().init(this, "https://YOUR_SERVER", "YOUR_APP_KEY", null, DeviceId.Type.OPEN_UDID)

For all of those different approaches, Countly.sharedInstance().init(...) method should be called from your Application subclass (preferred), or from your main activity onCreate method.

In the case of OpenUDID you'll need to include following declaration into your AndroidManifest.xml:

<service android:name="org.openudid.OpenUDID_service">
        <action android:name="org.openudid.GETUDID" />

In the case of Google Advertising ID, please make sure that you have Google Play services 4.0+ included into your project. Also note that Advertising ID silently falls back to OpenUDID in case it failed to get Advertising ID when Google Play services are not available on a device.

After Countly.sharedInstance().init(...) call you'll need to add following calls to all your activities:

  • Call Countly.sharedInstance().onStart(this) in onStart, where this is a link to the current Activity.
  • Call Countly.sharedInstance().onStop() in onStop.

If the "onStart" and "onStop" calls are not added some functionality will not work, for example sessions will not be tracked. The countly "onStart" has to be called in the activities "onStart" function, it can not be called in "onCreate" or any other place otherwise the application will receive exceptions.

Additionally, make sure that INTERNET permission is set if there's none in your manifest file.

Enabling logging

If logging is enabled then our sdk will print out debug messages about it's internal state and encountered problems.

When advise doing this while implementing countly features in your application.


Changing a device ID

In case your application authenticates users, you can also change device ID to your user ID later. This helps you identify a specific user with a specific ID on a device she logs in, and the same scenario can also be used in cases this user logs in using a different way (e.g tablet, another mobile phone or web). In this case any data stored in Countly server database and associated with temporary device ID will be transferred into user profile with device id you specified in the following method call:

Countly.sharedInstance().changeDeviceId("new device ID")

Whenever your authenticated user logs out, in case you want to track history and further activity under another Countly user, call:

Countly.sharedInstance().changeDeviceId(DeviceId.Type.OPEN_UDID, null)

You can also set Advertising ID as your device ID generation strategy or even supply your own string with DeviceId.Type.DEVELOPER_SPECIFIED type.

Retrieving the device id and its type

You may wanty to see what device id Countly is assigning for the specific device and what the source of that id is. For that you may use the following calls. The id type is an enum with the possible values of: "DEVELOPER_SUPPLIED", "OPEN_UDID", "ADVERTISING_ID".

String usedId = Countly.sharedInstance().getDeviceID();
Type idType = Countly.sharedInstance().getDeviceIDType();


If the data sent to the server is short enough, the sdk will use HTTP GET requests. In case you want an override so that HTTP POST is used in all cases, call the "setHttpPostForced" function after you called "init". You can use the same function to later in the apps life cycle disable the override. This function has to be called every time the app starts.

//the init call before the override
Countly.sharedInstance().init(this, "https://YOUR_SERVER", "YOUR_APP_KEY", "YOUR_DEVICE_ID")
//enabling the override
//disabling the override

Parameter Tampering Protection

You can set optional salt to be used for calculating checksum of request data, which will be sent with each request using &checksum field. You need to set exactly the same salt on Countly server. If salt on Countly server is set, all requests would be checked for validity of &checksum field before being processed.


Using Proguard

Proguard obfuscates OpenUDID & Countly Messaging classes. So if you use OpenUDID or Countly Messaging in your application, you need to add following lines to your Proguard rules file:

-keep class org.openudid.** { *; }
-keep class** { *; }

Note: Make sure you use App Key (found under Management -> Applications) and not API Key. Entering API Key will not work.

Setting up custom events

A custom event is any type of action that you can send to a Countly instance, e.g purchase, settings changed, view enabled and so. This way it's possible to get much more information from your application compared to what is sent from Android SDK to Countly instance by default.

Data passed should be in UTF-8

All data passed to Countly server via SDK or API should be in UTF-8.

As an example, we will be recording a purchase event. Here is a quick summary what information each usage will provide us:

  • Usage 1: how many times purchase event occured.
  • Usage 2: how many times purchase event occured + the total amount of those purchases.
  • Usage 3: how many times purchase event occured + which countries and application versions those purchases were made from.
  • Usage 4: how many times purchase event occured + the total amount both of which are also available segmented into countries and application versions.
  • Usage 5: how many times purchase event occured + the total amount both of which are also available segmented into countries and application versions + the total duration of those events.

1. Event key and count

Countly.sharedInstance().recordEvent("purchase", 1);

2. Event key, count and sum

Countly.sharedInstance().recordEvent("purchase", 1, 0.99);

3. Event key and count with segmentation(s)

HashMap<String, String> segmentation = new HashMap<String, String>();
segmentation.put("country", "Germany");
segmentation.put("app_version", "1.0");

Countly.sharedInstance().recordEvent("purchase", segmentation, 1);

4. Event key, count and sum with segmentation(s)

HashMap<String, String> segmentation = new HashMap<String, String>();
segmentation.put("country", "Germany");
segmentation.put("app_version", "1.0");

Countly.sharedInstance().recordEvent("purchase", segmentation, 1, 0.99);

5. Event key, count, sum and duration with segmentation(s)

HashMap<String, String> segmentation = new HashMap<String, String>();
segmentation.put("country", "Germany");
segmentation.put("app_version", "1.0");

Countly.sharedInstance().recordEvent("purchase", segmentation, 1, 0.99, 60);

Those are only a few examples with what you can do with custom events. You can extend those examples and use country, app_version, game_level, time_of_day and any other segmentation that will provide you valuable insights.

Timed events

It's possible to create to create timed events by defining a start and stop moment.

String eventName = "Custom event";

//start some event
//wait some time

//end the event 

When ending a event you can also provide additional information. But in that case you have to provide segmentation, count and sum. The default values for those are "null", 1 and 0.

String eventName = "Custom event";

//start some event
//wait some time

Map<String, String> segmentation = new HashMap<>();
segmentation.put("wall", "orange");

//end the event while also providing segmentation information, count and sum
Countly.sharedInstance().endEvent(eventName, segmentation, 4, 34);

User location

While integrating this SDK into your application, you might want to track your user location. You could use this information to better know your apps user base or to send them tailored push notifications based on their coordinates. There are 4 fields that can be provided:

  • country code in the 2 letter iso standard
  • city name (has to be set together with country code)
  • Comma separate latitude and longitude values, for example "56.42345,123.45325"
  • ip address of your user
//set user location
String countryCode = "us";
String city = "Houston";
String latitude = "29.634933";
String longitude = "-95.220255";
String ipAddress = null;

Countly.sharedInstance().setLocation(countryCode, city, latitude + "," + longitude, ipAddress);

When those values are set, they will be sent every time when initiating a session. If they are set after a session was initiated, a separate request will also be sent. Except for ip address, because Countly Server processes ip address only when starting a session.

If you don't want to set specific fields, set them to null.

Users might want to opt out of location tracking. To do that, call:

//disable location

It will erase cached location data from device and stop further tracking.

If, after disabling location, "setLocation" is called with any non null value, tracking will resume.

Attribution analytics & install campaigns

Countly Attribution Analytics allows you to measure your marketing campaign performance by attributing installs from specific campaigns. This feature is available for Countly Enterprise.

In order to get more precise attribution on Android it is highly recommended to allow Countly to listen to INSTALL_REFERRER intent and you can do that by adding following XML code to your AndroidManifest.xml file, inside application tag.

<receiver android:name="" android:exported="true">
		<action android:name="" />

Note that modifying AndroidManifest.xml file is the only thing you would need to do, in order to start getting data from your campaigns via Attribution Analytics plugin.

For more information about how to setup your campaigns, please see this documentation.

Getting user feedback

There are two ways of getting feedback from your users: Star rating dialog, feedback widget.

Star rating dialog allows users to give feedback as a rating from 1 to 5. The feedback widget allows to get the same 1 to 5 rating and also a text comment.

Feedback widget

Feedback widget shows a server configured widget to your user devices.

It's possible to configure any of the shown text fields and replace with a custom string of your choice.

In addition to a 1 to 5 rating, it is possible for users to leave a text comment and also leave a email in case the user would want some contact from the app developer.

Trying to show the rating widget is a single call, but underneath is a two step process. Before it is shown, the SDK tries to contact the server to get more information about the dialog. Therefore a network connection to it is needed.

You can try to show the widget after you have initialized the SDK. To do that, you first have to get the widget ID from your server:

Using that you can call the function to show the widget popup:

String widgetId = "xxxxx";
String closeButtonText = "Close";
Countly.sharedInstance().showFeedbackPopup(widgetId, closeButtonText, activity, new CountlyStarRating.FeedbackRatingCallback() {
  public void callback(String error) {
    if(error != null){
      Toast.makeText(activity, "Encountered error while showing feedback dialog: [" + error + "]", Toast.LENGTH_LONG).show();

Star rating dialog

Star rating integration provides a dialog for getting user's feedback about the application. It contains a title, simple message explaining what it is for, a 1-to-5 star meter for getting users rating and a dismiss button in case the user does not want to give a rating.

This star-rating has nothing to do with Google Play Store ratings and reviews. It is just for getting a brief feedback from users, to be displayed on the Countly dashboard. If the user dismisses star rating dialog without giving a rating, the event will not be recorded.

Star-rating dialog's title, message and dismiss button text can be customized either through the init function or the SetStarRatingDialogTexts function. If you don't want to override one of those values, set it to "null".

//set it through the init function
Countly.sharedInstance().init(context, serverURL, appKey, deviceID, idMode, starRatingLimit, starRatingCallback, "Custom title", "Custom message", "Custom dismiss button text");

//Use the designated function:
Countly.sharedInstance().setStarRatingDialogTexts(context, "Custom title", "Custom message", "Custom dismiss button text");

Star rating dialog can be displayed in 2 ways:

  • Manually, by developer
  • Automatically, depending on session count

In order to display the Star rating dialog manually, you must call the ShowStarRating function. Optionally, you can provide the callback functions. There is no limit on how many times star-rating dialog can be displayed manually.

//show the star rating without a callback
Countly.sharedInstance().showStarRating(context, null);

//show the star rating with a callback
Countly.sharedInstance().showStarRating(context, callback)

Star rating dialog will be displayed automatically when application's session count reaches the specified limit, once for each new version of the application. This session count limit can be specified on initial configuration or through the SetAutomaticStarRatingSessionLimit function. The default limit is 3. Once star rating dialog is displayed automatically, it will not be displayed again unless there is a new app version.

To show the automatic star rating dialog you need to pass the activity context during init.

//set the rating limit through the init function
int starRatingLimit = 5;
Countly.sharedInstance().init(context, serverURL, appKey, deviceID, idMode, starRatingLimit, starRatingCallback, starRatingTextTitle, starRatingTextMessage, starRatingTextDismiss);

//set it through the designated function
Countly.sharedInstance().starRatingLimit(context, 5);

If you want to enable the automatic star rating functionality, use SetIfStarRatingShownAutomatically function. It is disabled by default.

//enable automatic star rating

//disable automatic star rating

If you want to have the star rating shown only once per app's lifetime and not for each new version, use the "SetStarRatingDisableAskingForEachAppVersion" function.

//disable star rating for each new version

//enable star rating for each new version

The star rating callback provides functions for two events. OnRate is called when the user chooses a rating. OnDismiss is called when the user clicks the back button, clicks outside the dialog or clicks the "Dismiss" button. The callback provided in the init function is used only when showing the automatic star rating. For the manual star rating only the provided callback will be used.

CountlyStarRating.RatingCallback callback = new CountlyStarRating.RatingCallback() {
    public void OnRate(int rating) {
    	//the user rated the app

    public void OnDismiss() {
    	//the star rating dialog was dismissed

Remote Config

Remote config allows you to modiffy how your app functions or looks by requesting key-value pairs from your Countly server. The returned values can be modiffied based on the user profile. For more details please see Remote Config documentation.

Automatic Remote Config download

There are two ways of acquiring remote config data, by automatic download or manual request. By default, automatic remote config is disabled and therefore without developer intervention no remote config values will be requested.

Automatic value download happens when the SDK is initiated or when the device ID is changed. To enable it, you have to call setRemoteConfigAutomaticDownload before init. As a optional value you can provide a callback to be informed when the request is finished.

Countly.sharedInstance().setRemoteConfigAutomaticDownload(true, new RemoteConfig.RemoteConfigCallback() {
            public void callback(String error) {
                if(error == null) {
                    Toast.makeText(activity, "Automatic remote config download has completed", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(activity, "Automatic remote config download encountered a problem, " + error, Toast.LENGTH_LONG).show();
Countly.sharedInstance().init(appC, COUNTLY_SERVER_URL, COUNTLY_APP_KEY);

If the callback returns a non null value, then you can expect that the request failed and no values where updated.

When doing a automatic update, all locally stored values are replaced with the ones received (all locally stored ones are deleted and in their place are put new ones). It is possible that a previously valid key returns no value after a update.

Manual Remote Config download

There are three ways for manually requesting remote config update: * Manually updating everything * Manually updating specific keys * Manually updating everything except specific keys

Each of these requests also has a callback. If that returns a non null value, the request encountered some error and failed.

Functionally the manual update for everything remoteConfigUpdate is the same as the automatic update - replaces all stored values with the ones from the server (all locally stored ones are deleted and in their place are put new ones). The advantage is that you can make the request whenever it is desirable for you. It has a callback to let you know when it has finished.

Countly.sharedInstance().remoteConfigUpdate(new RemoteConfig.RemoteConfigCallback() {
            public void callback(String error) {
                if(error == null) {
                    Toast.makeText(activity, "Update finished", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(activity, "Error: " + error, Toast.LENGTH_SHORT).show();

You might want to update only specific key values. For that you need to call updateRemoteConfigForKeysOnly with a list of keys you want to be updated. That list is a array with string values of those keys. It has a callback to let you know when the request has finished.

Countly.sharedInstance().updateRemoteConfigForKeysOnly(new String[]{"aa", "dd"}, new RemoteConfig.RemoteConfigCallback() {
            public void callback(String error) {
                if(error == null) {
                    Toast.makeText(activity, "Update with inclusion finished", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(activity, "Error: " + error, Toast.LENGTH_SHORT).show();

You might want to update all values except a few defined keys, for that call updateRemoteConfigExceptKeys. The key list is a array with string values of the keys. It has a callback to let you know when the request has finished.

Countly.sharedInstance().updateRemoteConfigExceptKeys(new String[]{"aa", "dd"}, new RemoteConfig.RemoteConfigCallback() {
            public void callback(String error) {
                if (error == null) {
                    Toast.makeText(activity, "Update with exclusion finished", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(activity, "Error: " + error, Toast.LENGTH_SHORT).show();

When making requests with a "inclusion" or "exclusion" array, if those arrays ar empty or null, they will function the same as a simple manual request and will update all values. This means that it will also erase all keys not returned by the server.

Getting Remote Config values

To request a stored value, call getRemoteConfigValueForKey with the specified key. If it returns null then no value was found. The SDK has no knowledge of the returned value type and therefore returns a Object. The developer needs to cast it to the appropiate type. The returned values can also be a JSONArray, JSONObject or just a simple value like int.

Object value_1 = Countly.sharedInstance().getRemoteConfigValueForKey("aa");
Object value_2 = Countly.sharedInstance().getRemoteConfigValueForKey("bb");
Object value_3 = Countly.sharedInstance().getRemoteConfigValueForKey("cc");
Object value_4 = Countly.sharedInstance().getRemoteConfigValueForKey("dd");

int int_value = (int) value_1;
double double_value = (double) value_2;
JSONArray jArray = (JSONArray) value_3;
JSONObject jobj = (JSONObject) value_4;

Clearing Stored Remote Config values

At some point you might want to erase all values downloaded from the server. To achieve that you need to call one function.


Setting up User Profiles

Available with Enterprise Edition, User Profiles is a tool which helps you identify users, their devices, event timeline and application crash information. User Profiles can contain any information that either you collect, or is collected automatically by Countly SDK.

You can send user related information to Countly and let Countly dashboard show and segment this data. You may also send a notification to a group of users. For more information about User Profiles, see this documentation.

To provide information about the current user, you must call the Countly.userData.setUserData function. You can call it by providing a bundle of only the predefined fields or call it while also providing a second bundle of fields with your custom keys. After you have provided user profile information, you must save it by calling

//Update the user profile using only predefined fields
Map<String, String> predefinedFields = new HashMap<>();

//Update the user profile using predefined and custom fields
Map<String, String> predefinedFields = new HashMap<>();
Map<String, String> customFields = new HashMap<>();
Countly.userData.setUserData(predefinedFields, customFields);

The keys for predefined user data fields are as follows:

Key Type Description
name String User's full name
username String User's nickname
email String User's email address
organization String User's organisation name
phone String User's phone number
picture String URL to avatar or profile picture of the user
picturePath String Local path to user's avatar or profile picture
gender String User's gender as M for male and F for female
byear String User's year of birth as integer

Using "" for strings or a negative number for 'byear' will effectively delete that property.

For custom user properties you may use any key values to be stored and displayed on your Countly backend. Note: keys with . or $ symbols will have those symbols removed.

Modifying custom data

Additionally you can do different manipulations on your custom data values, like increment current value on server or store a array of values under the same property.

Below is the list of available methods:

//set one custom properties
Countly.userData.setProperty("test", "test");
//increment used value by 1
//increment used value by provided value
Countly.userData.incrementBy("used", 2);
//multiply value by provided value
Countly.userData.multiply("used", 3);
//save maximal value
Countly.userData.saveMax("highscore", 300);
//save minimal value
//set value if it does not exist
Countly.userData.setOnce("tag", "test");
//insert value to array of unique values
Countly.userData.pushUniqueValue("type", "morning");
//insert value to array which can have duplocates
Countly.userData.pushValue("type", "morning");
//remove value from array
Countly.userData.pullValue("type", "morning");

//send provided values to server;

In the end always call to send them to the server.

User Consent management

This feature is available from 18.04

To be compliant with GDPR, starting from 18.04, Countly provides ways to toggle different Countly features on/off depending on the given consent.

More information about GDPR can be found here.

By default the requirement for consent is disabled. To enable it, you have to call setRequiresConsent with true, before initializing Countly.

Countly.sharedInstance().init(appC, COUNTLY_SERVER_URL, COUNTLY_APP_KEY);

By default no consent is given. That means that if no consent is enabled, Countly will not work and no network requests, related to features, will be sent. When consent status of a feature is changed, that change will be sent to the Countly server.

For all features, except push, consent is not persistent and will have to be set every time before countly init. Therefore the storage and persistance of given consent falls on the sdk integrator.

Consent for features can be given and revoked at any time, but if it is given after Countly init, some features might work partially.

If consent is removed, but the appropriate function can't be called before the app closes, it should be done at next app start so that any relevant server side features could be disabled (like reverse geo ip for location)

Feature names in the Android SDK are stored as static fields in the class called CountlyFeatureNames.

The current features are: * sessions - tracking when, how often and how long users use your app * events - allow sending custom events to server * views - allow tracking which views user visits * location - allow sending location information * crashes - allow tracking crashes, exceptions and errors * attribution - allow tracking from which campaign did user come * users - allow collecting/providing user information, including custom properties * push - allow push notifications * starRating - allow to send their rating and feedback

Feature groups

Features can be grouped into groups. With this you can give/remove consent to multiple features in the same call. They can be created using createFeatureGroup. Those groups are not persistent and have to be created on every restart.

// prepare features that should be added to the group
String[] groupFeatures = new String[]{ Countly.CountlyFeatureNames.sessions, Countly.CountlyFeatureNames.location };

// create the feature group
Countly.sharedInstance().createFeatureGroup("groupName", groupFeatures);

Changing consent

There are 3 ways of changing feature consent: * giveConsent/removeConsent - gives or removes consent to a specific feature

// give consent to "sessions" feature
Countly.sharedInstance().giveConsent(new String[]{Countly.CountlyFeatureNames.sessions});

// remove consent from "sessions" feature
Countly.sharedInstance().removeConsent(new String[]{Countly.CountlyFeatureNames.sessions});
  • setConsent - set consent to a specific (true/false) value
// give consent to "sessions" feature
Countly.sharedInstance().setConsent(new String[]{Countly.CountlyFeatureNames.sessions}, true);

// remove consent from "sessions" feature
Countly.sharedInstance().setConsent(new String[]{Countly.CountlyFeatureNames.sessions}, false);
  • setConsentFeatureGroup - set consent for a feature group to a specific (true/false) value
// prepare features that should be added to the group
String[] groupFeatures = new String[]{ Countly.CountlyFeatureNames.sessions, Countly.CountlyFeatureNames.location };

String groupName = "featureGroup1";

// give consent to "sessions" feature
Countly.sharedInstance().setConsentFeatureGroup(groupName, true);

// remove consent from "sessions" feature
Countly.sharedInstance().setConsentFeatureGroup(groupName, false);

Setting up push notifications

Read this important notice before integrating push notifications

In order to use push notifications for Android, you'll need to include a different version of SDK into your project. We have 3 library projects in Maven, Jcenter and Github: sdk, sdk-messaging & sdk-messaging-fcm for no push integration needed, GCM integration & FCM integration respectively. If you want messaging support, include second one (it also adds a dependency on "play-services-gcm") or third one (you'll need to add Firebase to your dependencies yourself).

Countly SDK versions prior to 18.04 supported only GCM notifications, while version 18.04 implements both: GCM (sdk-messaging dependency) and FCM integration (sdk-messaging-fcm dependency). We recommend switching to FCM as soon as possible. Migration is seamless since old GCM users will continue to receive your notifications even after you update SDK & server key.

To upgrade SDK from GCM to FCM, you'll need to follow these steps:

  • Migrate your GCM project to Firebase if not done yet.
  • Remove old GCM server key from Countly dashboard (Applications -> Management) and add FCM server key.
  • Update your app by including Firebase SDK into it.
  • Remove sdk-messaging dependency from your app and add sdk-messaging-fcm one.

Getting GCM / FCM credentials

Countly needs server key to authenticate with GCM or FCM. In case your app is still not migrated to Firebase, please do that.

Then open Firebase console and open Project settings:

Select Cloud Messaging tab

Copy & paste either GCM key (deprecated), or FCM key (only SDK 18.04+) into your application GCM/FCM credentials upload form in Countly dashboard, hit Validate and eventually save changes. For GCM (deprecated) you'll also need Sender ID to pass into initMessaging SDK call.

FCM integration (recommended)

Please check our Demo app for a complete integration example.

Once you followed Google guide for Adding Firebase to your project, setting up Countly FCM is quite easy.

Adding dependencies

At first, add required dependencies to your build.gradle: (use latest SDK version below).

implementation ''
implementation ''

Then add CountlyPush.init() call to your Application subclass or main activity onCreate(). Here we use Application subclass called App. Don't forget that Android O and later require use of NotificationChannels. Use CountlyPush.CHANNEL_ID for Countly-displayed notifications:

public class App extends Application {

    public void onCreate() {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (notificationManager != null) {
                // Create the NotificationChannel
                NotificationChannel channel = new NotificationChannel(CountlyPush.CHANNEL_ID, getString(R.string.countly_hannel_name), NotificationManager.IMPORTANCE_DEFAULT);

                .init(this, "", "APP_KEY");

        CountlyPush.init(this, Countly.CountlyMessagingMode.PRODUCTION);

                .addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
                    public void onComplete(@NonNull Task<InstanceIdResult> task) {
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "getInstanceId failed", task.getException());

                        // Get new Instance ID token
                        String token = task.getResult().getToken();

Please note that in CountlyPush.init() you also specify mode of your token - test or production. It's quite handy to separate test devices from production ones by changing CountlyMessagingMode so you could test notifications before sending to all users.

Now we need to add the Service. Add a service definition to your AndroidManifest.xml:

<service android:name=".DemoFirebaseMessagingService">
        <action android:name="" />

... and a class for it:

public class DemoFirebaseMessagingService extends FirebaseMessagingService {
    private static final String TAG = "DemoMessagingService";

    public void onNewToken(String token) {

        Log.d("DemoFirebaseService", "got new token: " + token);

    public void onMessageReceived(RemoteMessage remoteMessage) {

        Log.d("DemoFirebaseService", "got new message: " + remoteMessage.getData());

        // decode message data and extract meaningful information from it: title, body, badge, etc.
        CountlyPush.Message message = CountlyPush.decodeMessage(remoteMessage.getData());

        if (message != null && message.has("typ")) {
            // custom handling only for messages with specific "typ" keys

        Intent notificationIntent = null;
        if (message.has("anotherActivity")) {
            notificationIntent = new Intent(getApplicationContext(), AnotherActivity.class);
        Boolean result = CountlyPush.displayMessage(getApplicationContext(), message, R.drawable.ic_message, notificationIntent);
        if (result == null) {
            Log.i(TAG, "Message wasn't sent from Countly server, so it cannot be handled by Countly SDK");
        } else if (result) {
            Log.i(TAG, "Message was handled by Countly SDK");
        } else {
            Log.i(TAG, "Message wasn't handled by Countly SDK because API level is too low for Notification support or because currentActivity is null (not enough lifecycle method calls)");

    public void onDeletedMessages() {

This class is responsible for token changes and message handling logic. Countly provides default UI for your notifications which would display a Notification if your app is in background or Dialog if it's active. It will also automatically report button clicks back to the server for Actioned metric conversion tracking. But using it or not is completely up to you. Let's have an overview of onMessageReceived method:

  1. It calls CountlyPush.decodeMessage() to decode message from Countly-specific format. This way you'll have way to access standard fields like badge, url or your custom data keys.
  2. Then it checks if message has typ custom data key and if it does, just records Actioned metric. Let's assume it's your custom notification to preload some data from remote server. Our demo app has a more in-depth scenario for this case.
  3. In case message also has anotherActivity custom data key, it creates a notificationIntent to launch activity named AnotherActivity. This intent is only used as default content intent for user tap on a Notification. For Dialog case it's not used.
  4. Then the service calls CountlyPush.displayMessage() to perform standard Countly notification displaying logic - Notification if your app is in background or not running and Dialog if it's in foreground. Note that this method takes an int resource parameter. It must be a compatible with corresponding version of Android notification small icon.

Apart from listed above, SDK also exposes methods CountlyPush.displayNotification() & CountlyPush.displayDialog() in case you only need Notifications and don't want Dialog or vice versa.

Example push notification payload sent from Countly server:

	collapse_key: “collapse_key”, // if present
	time_to_live: 123,
	data: {
		message: “message string”, // if present
		title: “title string”, // if present
		sound: “sound string”, // if present
		badge: 123, // if present
		c.i: “message id string”,
		c.l: “http://message-wide-url”, // if present
		c.m: “”, // if present
		c.s: “true”, // if sound & message absent
		c.b: [ // if present
			{t: “Button 1 title”, l: “http://button.1.url”},
			{t: “Button 2 title”, l: “http://button.2.url”} // if present
		// any other data properties if present

Legacy GCM integration (deprecated)

For GCM integration (sdk-messaging dependency) you'll need Sender ID. You can get one either from Firebase console (see above), or, in case you haven't yet migrated to Firebase, from Google Developers Console. Project Number below is your Sender ID. Note that this is different from the Project ID.

Check that GCM (Google Cloud Messaging) service is enabled for your application:

As the final step, get a server token from Credentials menu of APIs & auth section:

Make sure you don't enter any IP address restrictions or make sure those restrictions you've entered play nice with your Countly server.

Add extra lines in AndroidManifest.xml

Make sure your application requests these permissions (replace with your app package) in the apps manifest file, as shown below:

<permission android:name="" android:protectionLevel="signature" />
<uses-permission android:name="" />

Note that we use Advertising ID for device ID generation purposes in this example. If you want OpenUDID or have your own unique device ID, refer to documentation for details.

And then, change the way you init Countly by adding an additional call to initMessaging:

    .init(this, "YOUR_SERVER", "APP_KEY", null, DeviceId.Type.ADVERTISING_ID)
    .initMessaging(this, YourActivity.class, "PROJECT_NUMBER", Countly.CountlyMessagingMode.TEST);

Here, starting with line .initMessaging, first parameter is an activity which would be started when user clicks on notification, second parameter is its class, and PROJECT_NUMBER is Project Number retrieved from your Google Developers Console. Last parameter controls whether this device is recorded as a test device on Countly server, or as a production device. This would allow you to send messages either to test (using .TEST), or to production (using .PRODUCTION) users only, so you could separate user bases in order to make initial tests.

Behind the scenes, Countly will add latest Google Play Services dependency and add some configuration options to your AndroidManifest.xml during manifest merging by gradle. See sdk-messaging's build.gradle and AndroidManifest.xml for details.

If you want to get notified whenever new message arrives (optional step), register your BroadcastReceiver like this:

protected void onResume() {

    /** Register for broadcast action if you need to be notified when Countly message received */
    messageReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            Message message = intent.getParcelableExtra(CountlyMessaging.BROADCAST_RECEIVER_ACTION_MESSAGE);
            Log.i("CountlyActivity", "Got a message with data: " + message.getData());
    IntentFilter filter = new IntentFilter();
    registerReceiver(messageReceiver, filter);

protected void onPause() {

    /** Don't forget to unregister receiver */

Automatic message handling

Countly handles most common message handling tasks for you. For example, it generates and shows Notifications or Dialogs and tracks conversion rates automatically. In most cases you don't need to know how it works, but if you want to customize the behavior or exchange it with your own implementation, here is a more in depth explanation of what it does:

First the received notification payload is analyzed and if it's a Countly notification (has "c" dictionary in payload), processes it. Otherwise, or if the notification analysis says that is a Data-only notification (you're responsible for message processing), it does nothing.

After that it automatically makes callbacks to Countly Messaging server to calculate number of push notifications open and number of notifications with positive reactions.

Here are explanations of common usage scenarios that are handled automatically: * Doesn't do anything except for conversions tracking if you specify that it is a Data-only notification in the dashboard. This effectively sets a special flag in message payload so you could process it on your own. * It displays a Notification whenever a message arrives and your application is in background. * It displays a Dialog when a new message arrives and your application is in foreground. * It displays a Dialog when a new message with a action arrives (open URL), and user responds to it by swiping or tapping notification.

A Dialog always has a message, but the buttons set displayed depends on the message type:

  • For notifications without any actions (just a text message) it displays a single Cancel button.
  • For notifications with a URL (you ask user to open a link to some blog post, for instance) it displays Cancel & Open buttons.
  • For notifications with custom buttons it displays corresponding buttons.

Using Android deep links

When using Countly push notifications, you can benefit from Android deep links in your application for the buttons you provide. Those are basically links for specific activities of your application. A link can either be generic http link like or a link with a custom URI (uniform resource indicator) scheme like otherexample://things.

In order for Android deep links to work, you need to specify intent filters in your application's manifest for specific groups of links you want to use.

A deeper guide on how to configure your application to use deep links can be found here.

Developer-overridden message handling

You can also completely disable push notification handling made by Countly SDK. To do that just add true to the end of your initMessaging() call:

    .init(this, "YOUR_SERVER", "APP_KEY", null, DeviceId.Type.ADVERTISING_ID)
    .initMessaging(this, CountlyActivity.class, "PROJECT_NUMBER", Countly.CountlyMessagingMode.TEST, true);

This parameter effectively disables any UI interactions and Activity instantiation from Countly SDK. To enable custom processing of push notifications you can either register your own WakefulBroadcastReceiver, or use our example with broadcast action. Once you switched off default push notification UI, please make sure to call CountlyMessaging.recordMessageOpen(id) whenever push notification is delivered to your device and CountlyMessaging.recordMessageAction(id, index) whenever user positively reacted on your notification. id is a message id string you can get from c.i key of push notification payload. index is optional and used to identify type of action: 0 for tap on notification in drawer, 1 for first button of rich push, 2 for second one.

Handling button or push clicks

When receiving a push notification, the user can click directly on it, or a button it has. When user clicks on anywhere in the push notification, an intent is launched to open the provided link. This can be a web page URL or a deep link. If you have configured your app so that opening launching this intent will open a activity of your app, it should be possible to track which buttons was pressed.

There is is also the option to add additional meta data to those intents. The included meta information contains information such as which buttons is pressed, the link given in the notification, the title and the message of the notification.

By default this functionality is disabled and they are added as extras to the intent.

Available after version 17.09

This feature is available for Android SDK 17.09 or later

In order to enable this functionality, you need to call this function before initializing Countly messaging:


To access those extras from the intent, you should use these names:


To read the extra from the Intent, you would use something similar to this:

String buttonUrl = intent.getStringExtra(ProxyActivity.intentExtraButtonLink);

Setting up crash reporting

Countly SDK for Android has an ability to collect crash reports which you can examine and resolve later on the server.

Enabling crash reporting

Following function enables crash reporting, that will automatically catch uncaught Java exceptions.


Adding a custom key-value segment to a crash report

You can add a key/value segments to crash report, like for example, which specific library or framework version you used in your app, so you can figure out if there is any correlation between specific library or other segment and crash reports.

Use the following function for this purpose:

Countly.sharedInstance().setCustomCrashSegments(Map<String, String> segments)

Adding breadcrumbs

Following command adds crash breadcrumb like log record to the log that will be send together with crash report.

Countly.sharedInstance().addCrashLog(String record) 

Logging handled exceptions

You can also log handled exceptions on monitor how and when they are happening with the following command:

Countly.sharedInstance().logException(Exception exception)

Additional SDK features


You've probably noticed that we used Countly.CountlyMessagingMode.TEST in our example. That is because we're building the application for testing purposes for now. Countly separates users who run apps built for test and for release. This way you'll be able to test messages before sending them to all your users. When you're releasing the app, please use Countly.CountlyMessagingMode.PRODUCTION.

Push Notifications localization

While push notification messages in Countly Messaging are properly localized, you can also localize the way notifications are displayed. By default, Countly uses your application name for a title of notification alert and the English word "Open" for the alert button name. If you want to customize it, pass an array of Strings, where the button name is the first value, to initMessaging call:

String[] pushLocalizationArray = new String[]{"Open"};
    .init(this, "YOUR_SERVER", "APP_KEY", null, DeviceId.Type.ADVERTISING_ID)
    .initMessaging(this, CountlyActivity.class, "PROJECT_ID", Countly.CountlyMessagingMode.TEST, pushLocalizationArray);

Geolocation-aware notifications (Enterprise Edition only)

You can send notifications to users located at predefined locations. By default, Countly uses geoip database in order to bind your app users to their location. But if your app has access to better location data, you can submit it to the server:

String latitude = "57.708358";
String longitude = "11.974950";

Countly.sharedInstance().setLocation(null, null, latitude + "," + longitude, null)

View tracking

View tracking is a means to report every screen view to Countly dashboard. In order to enable automatic view tracking, call:


The tracked views will use the full activity names which includes their package name. It would look similar to "".

Short view names available after version 17.09

It is possible to use short view names which will use the simple activity name. That would look like "activityname". To use this functionality, call this before calling init:


Also you can track custom views with following code snippet:

Countly.sharedInstance().recordView("View name")

To review the resulting data, open the dashboard and go to Analytics > Views. For more information on how to use view tracking data to it's fullest potential, look for more information here.

Receiving and showing badge number from push notifications

Minimum Countly Server Version

This feature is supported only on servers with the minimum version 16.12.2.

While showing badges isn't supported natively for versions before Android O, there are some devices and launchers that support it. Therefore you may want to implement such a feature in your app but not that not all devices will support badges.

While creating a new message in the messaging overview and preparing it's content, there is a optional option called "Add iOS badge". You can use that to send badges also to Android devices.

In order to receive this badge number in your application, you have to subscribe to the broadcasts about received messages. There, you are informed about all received push notifications using Message and bundle. The badge number is sent with the key "badge". You can use that to extract the badge number from the received bundle and then use it to display badge numbers with your implementation of choice.

In the below example we will use a badge library called ShortcutBadger, which is used to show badges on Android. Follow their instructions in this link on how to use it in your Androidproject. You can also see the same example inside Countly messaging sample project.

/** Register for broadcast action if you need to be notified when Countly message received */
messageReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        Message message = intent.getParcelableExtra(CountlyMessaging.BROADCAST_RECEIVER_ACTION_MESSAGE);
        Log.i("CountlyActivity", "Got a message with data: " + message.getData());

        //Badge related things
        Bundle data = message.getData();
        String badgeString = data.getString("badge");
        try {
            int badgeCount = Integer.parseInt(badgeString);

            boolean succeded = ShortcutBadger.applyCount(getApplicationContext(), badgeCount);
            if(!succeded) {
                Toast.makeText(getApplicationContext(), "Unable to put badge", Toast.LENGTH_SHORT).show();
        } catch (NumberFormatException exception) {
            Toast.makeText(getApplicationContext(), "Unable to parse given badge number", Toast.LENGTH_SHORT).show();
IntentFilter filter = new IntentFilter();
registerReceiver(messageReceiver, filter);

Checking if init has been called

In case you want to check if init has been called, you can just use the following function:


Checking if onStart has been called

For some applications there might a use case where the developer would like to check if the Countly sdkonStart function has been called. For that they can use the following call:


Ignoring app crawlers

Sometimes server data might be polluted with app crawlers which are not real users, and you would like to ignore them. Starting from the 17.05 release it's possible to do that filtering on the app level. The current version does that using device names. Internally the Countly sdk has a list for crawler device names, if a device name matches one from that list, no information is sent to the server. At the moment that list has only one entry: "Calypso AppCrawler". In the future we might add more crawler device names if such are reported. If you have encountered a crawler that is not in that list, but you would like to ignore, you can add it to your sdk list yourself by calling addAppCrawlerName. Currently by default the sdk is ignoring crawlers, if you would like to change that, use ifShouldIgnoreCrawlers. If you want to check if the current device was detected as a crawler, use isDeviceAppCrawler. Detection is done in the init function, so you would have to add the crawler names before that and do the check after that.

//set that the sdk should ignore app crawlers

//set that the sdk should not ignore app crawlers

//add another app crawler device name to ignore
Countly.sharedInstance().addAppCrawlerName("App crawler");

//returns true if this device is detected as a app crawler and false otherwise

Setting Custom HTTP header values

In case you want to add custom header key/value pairs to each request sent to the countly server, you can make the following call:

HashMap<String, String> customHeaderValues = new HashMap<>();
customHeaderValues.put("foo", "bar");


The provided values will override any previously stored value pairs. In case you want to erase previously stored pairs, provide null.

Native C++ Crash Reporting

Countly uses Google's Breakpad open source library to be able to report crashes that occured in C++ components of your application if you have any. Breakpad provides: a) a tool for creating symbol files from your object files (dump_syms) b) ability to detect crashes and record crashes via compact minidump files (crash handler) c) a tool for generating human readable stack traces by using symbol files and and crash minidump files.

Countly provides sdk_native Android library to add crash handler to your native code and create crash minidump files. SDK will check for those minidump files and send them automatically to your Countly server on application start. You can download sdk_native from default JCenter repository or Bintray Maven repository and include it in your project similar to how you included our SDK (please change LATEST_VERSION below by checking our maven page, currently it is 19.02.3):

// build gradle file 

repositories {
    maven {
        url ''

dependencies {
    implementation ''

Then call our init method as early as possible in your application life cycle to be able to catch crashes that occur during initialization:



getApplicationContext() is needed to determine a storage place for minidump files.

Automatic creation and upload of symbol files

You can create Breakpad symbols files yourself and upload them to your Countly server using our UI. They will be needed to create stack traces from minidump files. Countly also developed a Gradle plugin to automate this process. To use upload plugin in Studio we need to include it first (LATEST_VERSION is currently 19.02.3):

apply plugin: 

buildscript {
    repositories {
        maven {
            url ''
    // for LATEST_VERSION check
    dependencies {
        classpath group: '', 'name': 'sdk-plugin', 'version': 'LATEST_VERSION'

Then you need to configure a gradle countly block for the plugin:

countly {
    server "https://YOUR_SERVER", 
    app_key "YOUR_APP_KEY"  

Then you will have two new Gradle tasks available to you: uploadJavaSymbols and uploadNativeSymbols. uploadJavaSymbols is for uploading the mapping.txt file generated by Proguard. After building your project you can run these tasks through Studio's Gradle tool window (1). They will be available under your app (2) and grouped as countly tasks (3).

Another option is to run them from command line:

./gradlew uploadNativeSymbols

// or if you have subprojects

./gradlew :project-name:uploadNativeSymbols

You can also configure your build so these tasks will be run after every build:

tasks.whenTaskAdded { task ->
    if ('assemble')) {

In addition to specify your server and app info you can also override some default values in countly block.

countly {
  // required by both tasks
  server ""
  app_key "XXXXXX"  // same app_key used for SDK integration

  // optional properties for uploadJavaSymbols. Shown are the default values.

  // location of mapping.txt file relative to project build directory
  mappingFile "outputs/mapping/release/mapping.txt"

  // note that will be saved with the upload and can be checked in the UI
  noteJava "sdk-plugin automatic upload of mapping.txt"

  // optional properties for uploadNativeSymbols. Shown are the default values.

  // directory of .so files relative to project build directory.
  // you can check the tar.gz file created under intermediates/countly
  nativeObjectFilesDir "intermediates/cmake/release/obj"

  // path for breakpad tool dump_syms executable
  dumpSymsPath "/usr/bin/dump_syms"

  // note that will be saved with the upload and can be checked in the UI
  noteNative "sdk-plugin automatic upload of breakpad symbols"


Two of these properties are possibly needs to be configured by you: dumpSymsPath and nativeObjectFilesDir. Plugin assumes you will run the task after a release build. To test it for debug builds please change nativeObjectFilesDir to be "intermediates/cmake/debug/obj" (or whereever your build process puts .so files under build directory).

We created a sample app in our github repo that demostrates both how to use sdk-native and our upload plugin.

Building Android SDK

If you need to customize our Android SDK for your needs, you can find it here among our Countly Github repositories as an Android Studio project. Modules included in the project are:

Module Name Description
sdk Main functionality of Android SDK. No push notifcation
app Sample app to test sdk
sdk-messaging sdk + Push notifications (GCM integration). May be removed in upcoming releases.
app-messaging Sample app to test sdk-messaging
sdk-messaging-fcm Push notifications ( FCM integration).
app-messaging-fcm Sample app to test sdk-messaging-fcm
sdk-native Module needed for Native C++ crash reporting
app-native Sample app to test sdk-native

In recent Android Studio versions there is a bug you may encounter when you build the project in Studio. If you see a build error like SIMPLE: Error configuring please check your text view for build gradle output. If there is an error about CMake was unable to find a build program corresponding to "Ninja". CMAKE_MAKE_PROGRAM is not set then you need ninja to be available in your PATH. If you are using cmake embedded in Studio, ninja can be found at <sdk_location>/cmake/<cmake_version>/bin directory.

For the sdk-native module there is a build step which happens outside of Studio. You may find the related code and build scripts in sdk-native/src/cpp_precompilation. We are working on building breakpad library with an appropriate ndk version to integrate this step into Studio build. Meanwhile, it seems OK to use the library files in sdk-native/src/main/jniLibs/ that are externally built.

Looking for help?