Xamarin (Android)

Follow

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

Minimum Android version

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

Adding Countly SDK

You can use Visual Studio to add Countly SDK to your project. For this, add NuGet packages CountlySDK.Xamarin.Android ,CountlySDK.Messaging.Xamarin.Android or CountlySDK.Messaging.Xamarin.Android (if you require using push notifications in your project)

You can use these commands for convenience to install each package:

CountlySDK.Xamarin.Android:

Install-Package CountlySDK.Xamarin.Android

CountlySDK.Messaging.Xamarin.Android:

Install-Package CountlySDK.Messaging.Xamarin.Android

CountlySDK.Messaging.FCM.Xamarin.Android:

Install-Package CountlySDK.Messaging.FCM.Xamarin.Android

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 https://try.count.ly, https://us-try.count.ly or https://asia-try.count.ly. 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 https://example.com 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.AdvertisingId);

Or, you can use OpenUDID:

Countly.SharedInstance().Init(this, "https://YOUR_SERVER", "YOUR_APP_KEY", null, DeviceId.Type.OpenUdid);	

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 the following declaration into your AndroidManifest.xml:

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

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() in onStart.
  • 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 its internal state and encountered problems.

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

Countly.SharedInstance().SetLoggingEnabled(true);

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.OpenUdid, null);

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

Retrieving the device id and its type

You may want to see what device id Countly is assigning for the specific device and what the source of that it is. For that, you may use the following calls. The id type is an enum with the possible values of: "DeveloperSupplied", "OpenUdid", "AdvertisingId".

string usedId = Countly.SharedInstance().GetDeviceID();
Type idType = Countly.SharedInstance().GetDeviceIDType();

Forcing HTTP POST

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, set the "HttpPostForced" property after you called "Init". You can use the same function to later in the app's lifecycle 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
Countly.SharedInstance().HttpPostForced = true;
  
//disabling the override
Countly.SharedInstance().HttpPostForced = false;

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.

Countly.SharedInstance().EnableParameterTamperingProtection("salt");

Using Proguard

Proguard obfuscates OpenUDID and if you use it as for your ID, then Countly can't generate ID anymore, and thus can't send anything. Therefore you need to exclude OpenUDID from being obfuscated with the following one-liner:

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

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

Setting up events

An 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)

Dictionary<String, String> segmentation = new Dictionary<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)

Dictionary<String, String> segmentation = new Dictionary<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)

Dictionary<String, String> segmentation = new Dictionary<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 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
Countly.SharedInstance().StartEvent(eventName);
//wait some time

//end the event 
Countly.SharedInstance().EndEvent(eventName);

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
Countly.SharedInstance().StartEvent(eventName);
//wait some time

Dictionary<String, String> segmentation = new Dictionary<String, String>();
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

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

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
Countly.SharedInstance().DisableLocation();

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="ly.count.android.sdk.ReferrerReceiver" android:exported="true">
	<intent-filter>
		<action android:name="com.android.vending.INSTALL_REFERRER" />
	</intent-filter>
</receiver>

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

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

Getting user feedback

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.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.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
Countly.SharedInstance().SetIfStarRatingShownAutomatically(true);

//disable automatic star rating
Countly.SharedInstance().SetIfStarRatingShownAutomatically(false);

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
Countly.SharedInstance().SetStarRatingDisableAskingForEachAppVersion(true);

//enable star rating for each new version
Countly.SharedInstance().SetStarRatingDisableAskingForEachAppVersion(false);

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 dialogue 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.

Create a class which implements CountlyStarRating.IRatingCallback, implement the methods OnRating and OnDismiss as per need.

public void OnRate(int rating) {
    	//the user rated the app
    }

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

Take an instance of it in your Activity file.

CountlyStarRating.RatingCallback callback = new CountlyStarRating.RatingCallback();

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 the second bundle of fields with your custom keys. After you have provided user profile information, you must save it by calling Countly.userData.save().

//Update the user profile using only predefined fields
Dictionary<String, String> predefinedFields = new Dictionary<String, String>();
Countly.UserData.SetUserData(predefinedFields);
Countly.UserData.Save()


//Update the user profile using predefined and custom fields
Dictionary<String, String> predefinedFields = new Dictionary<String, String>();
Dictionary<String, String> customFields = new Dictionary<String, String>();
Countly.UserData.SetUserData(predefinedFields, customFields);
Countly.UserData.Save()

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
Countly.UserData.Increment("used");
//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
Countly.UserData.SaveMin("best_time",60);
//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
Countly.UserData.Save();

In the end, always call Countly.UserData.Save() 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().SetRequiresConsent(true);

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 persistence of given consent fall 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 events to the 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.

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 Xamarin Android, you'll need to include a different version of SDK into your project. We have 3 library projects CountlySDK..Xamarin.Android, CountlySDK.Messaging.Xamarin.Android & CountlySDK.Messaging.FCM.Xamarin.Android 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 the Countly dashboard (Applications -> Management) and add FCM server key.
  • Update your app by including Firebase SDK into it.
  • Remove CountlySDK.Messaging.Xamarin.Android dependency from your app and add CountlySDK.Messaging.FCM.Xamarin.Android 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 the 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)

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

Adding packages

Add Nuget packages for CountlySDK.Messaging.FCM.Xamarin.Android and Xamarin.Firebase.Messaging.

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 the use of NotificationChannels. Use CountlyPush.ChannelId for Countly-displayed notifications:

Setting up push notifications involves a few steps that should be completed on Google Developers Console & Firebase Console, your application and Countly dashboard.

With introduction of Firebase, Google deprecated GCM in favour of Firebase Cloud Messaging. Current Countly Android SDK 17.05 will always use GCM. All subsequent versions will use Firebase instead.

In order to use GCM with projects created before Firebase release, please read section Legacy GCM integration below. For new projects (or if legacy integration GCM key doesn't work), please follow these steps:

protected override void OnCreate(Bundle savedInstanceState)
{
  base.OnCreate(savedInstanceState);
  if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
  {
    // Register the channel with the system; you can't change the importance
    // or other notification behaviors after this
    NotificationManager notificationManager =
      (NotificationManager)GetSystemService(NotificationService);
    if (notificationManager != null)
    {
      NotificationChannel channel = new NotificationChannel(CountlyPush.ChannelId, GetString(Resource.String.countly_channel_name), Android.App.NotificationImportance.Default);
channel.Description = GetString(Resource.String.countly_channel_description);                  notificationManager.CreateNotificationChannel(channel);
    }
  }
  Countly.SharedInstance()
    .SetLoggingEnabled(true)
    .Init(this, "http://try.count.ly", "APP_KEY");            CountlyPush.Init(Application, Countly.CountlyMessagingMode.Production);
}

Please note that in CountlyPush.Init() you also specify the 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.

<service android:name=".DemoFirebaseInstanceIdService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
    </intent-filter>
</service>

Then create a class:

 [Service]
public class DemoFirebaseInstanceIdService : FirebaseInstanceIdService
{
  const string TAG = "MyFirebaseIIDService";
  public override void OnTokenRefresh()
  {
    var refreshedToken = FirebaseInstanceId.Instance.Token;
    Log.Debug(TAG, "Refreshed token: " + refreshedToken);
    SendRegistrationToServer(refreshedToken);
    CountlyPush.OnTokenRefresh(FirebaseInstanceId.Instance.Token);
  }
  void SendRegistrationToServer(string token)
  {
    // Add custom implementation, as needed.
  }
}

This ensures that whenever the token is changed, it's sent to the Countly server.

Now we need to add notification handling code. Add another service to your AndroidManifest.xml:

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

... and a class for it:

 [Service]
public class DemoFirebaseMessagingService : FirebaseMessagingService
{
  private static string TAG = "DemoMessagingService";
  
  public override void OnMessageReceived(RemoteMessage message)
  {
    base.OnMessageReceived(message);
    Console.WriteLine(("DemoFirebaseService", "got new message: " +
                       message.Data));
    // decode message data and extract meaningful information from it: title, body, badge, etc.
    CountlyPush.IMessage countlymessage =
      CountlyPush.DecodeMessage(message.Data);
    
    if (message != null && countlymessage.Has("typ"))
    {
      // custom handling only for messages with specific "typ" keys
      countlymessage.RecordAction(this);
      return;
    }
    Intent notificationIntent = null;
    if (countlymessage.Has("anotherActivity"))
    {
      // notificationIntent = new Intent(this, AnotherActivity.class);
    }
    var result = CountlyPush.DisplayMessage(this, countlymessage,
                                            Resource.Id.Icon,
                                            notificationIntent);
    if (result == null)
    {
      Console.WriteLine(TAG, "Message wasn't sent from Countly server, so it cannot be handled by Countly SDK");
    }
    else if ((bool)result)
    {
      Console.WriteLine(TAG, "Message was handled by Countly SDK");
    }
    else
    {
      Console.WriteLine(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)");
    }
  }
}

This class is responsible for 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 a way to access standard fields like badge, URL or your custom data keys.

  2. Then it checks if the 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 a 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 the background or not running and Dialog if it's in foreground. Note that this method takes an int resource parameter. It must be compatible with the 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.

Legacy GCM integration (deprecated)

For GCM integration (CountlySDK.Messaging.Xamarin.Android 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 ly.count.android.demo.messaging with your app package) in the apps manifest file, as shown below:

<permission android:name="ly.count.android.demo.messaging.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="ly.count.android.demo.messaging.permission.C2D_MESSAGE" />

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 http://github.com/Countly/countly-sdk-android documentation for details.

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

  Countly.SharedInstance().Init(this, "YOUR_SERVER", "APP_KEY", null, DeviceId.Type.AdvertisingId).InitMessaging(this, this.Class, "PROJECT_NUMBER", Countly.CountlyMessagingMode.Test);

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 http://github.com/Countly/countly-sdk-android documentation for details.

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

Countly.SharedInstance()
      .Init(this, "YOUR_SERVER", "APP_KEY", null, DeviceId.Type.AdvertisingId)
      .InitMessaging(this, this.Class, "PROJECT_NUMBER", Countly.CountlyMessagingMode.Test);

Here, starting with the line .InitMessaging, the first parameter is an activity which would be started when the user clicks on notification, the second parameter is its class, and PROJECT_NUMBER is Project Number retrieved from your Google Developers Console. The last parameter controls whether this device is recorded as a test device on the 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.

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

Create a CustomBroadcastReceiver class, inherit it with BroadcastReceiver abstract class found in LY.Count.Android.Sdk.Messaging. Implement the function OnReceive.

public void onReceive(Context context, Intent intent) {
            var message = Intent.GetParcelableExtra(CountlyMessaging.BroadcastReceiverActionMessage);
                   }

Update the OnResume and OnPause methods as:

protected void onResume()
 {
    base.onResume();

    /** Register for broadcast action if you need to be notified when Countly message received */
    messageReceiver = new CustomBroadcastReceiver();
     IntentFilter filter = new IntentFilter();
    filter.AddAction(CountlyMessaging.GetBroadcastAction(this));
    registerReceiver(messageReceiver, filter);
}

protected void onPause() 
{
    base.onPause();
    /** Don't forget to unregister receiver */
    unregisterReceiver(messageReceiver);
}

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 foreground.
  • 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 (see URL, review below), and user responds to it by swiping or tapping notification.
  • It displays a Dialog on a next application launch if user didn't respond (tapped) to a message with a action (see URL, review below) when it arrived.

A Dialog always has a message, but the button 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;

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:

Countly.SharedInstance()
    .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.

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.

Countly.SharedInstance().EnableCrashReporting();

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(Dictionary<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

Testing

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"};
Countly.SharedInstance()
    .Init(this, "YOUR_SERVER", "APP_KEY", null, DeviceId.Type.AdvertisingId)
    .InitMessaging(this, CountlyActivity.Class, "PROJECT_ID", Countly.CountlyMessagingMode.Test, pushLocalizationArray);

Geolocation-aware notifications (Countly Enterprise 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:

double latitude = 12;
double longitude = 22;

Countly.SharedInstance().SetLocation(latitude, longitude);

View tracking

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

Countly.SharedInstance().SetViewTracking(true);

Short view names available after version 17.09

t 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:

Countly.SharedInstance().SetAutoTrackingUseShortName(true);

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 by Android, 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 CountlySDK.Badge.Xamarin.Android, which is used to show badges on Android. Find the CountlySDK.Badge.Xamarin.Android NuGet package and include it in your project.

CountlySDK.Badge.Xamarin.Android:

Install-Package CountlySDK.Badge.Xamarin.Android -Version 1.0.1

Create a CustomBroadcastReceiver class, inherit it with BroadcastReciever abstract class found in LY.Count.Android.Sdk.Messaging. Implement the function OnReceive.

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 {
            Int32.TryParse(badgeString,out int badgeCount);

            bool succeded = ShortcutBadger.ApplyCount(this, badgeCount);
            if(!succeded) {
                Toast.MakeText(this, "Unable to put badge", ToastLength.Short);
            }
        } catch (NumberFormatException exception) {
            Toast.MakeText(getApplicationContext(), "Unable to parse given badge number", ToastLength.Short);
        }
    }

Checking if init has been called

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

var value = Countly.SharedInstance().IsInitialized();

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:

var value = Countly.SharedInstance().HasBeenCalledOnStart;

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
var value = Countly.SharedInstance().IfShouldIgnoreCrawlers;

//set that the sdk should not ignore app crawlers
var value = Countly.SharedInstance().IfShouldIgnoreCrawlers;

//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
var value = Countly.SharedInstance().IsDeviceAppCrawler();

Looking for help?