From ea5118a3aa79bc70297388ddb8d76db0f1605cda Mon Sep 17 00:00:00 2001
From: Arif Burak Demiray
- This document will guide you through the process of SDK installation and it applies
- to version 23.2.X.
+ This documentation is for the Countly CPP SDK version 23.2.X. The SDK source
+ code repository can be found
+ here.
Supported Platforms are Windows, GNU/Linux, and Mac OS X.
- It is an open-source SDK, you can take a look at our SDK code in the
- Github repo
-
- Supported Platforms: Windows, GNU/Linux, and Mac OS X.
+ To examine the example integrations please have a look
+ here.
@@ -22,26 +21,26 @@
to run on most platforms. To build this SDK, you need:
First, clone the repository with its submodules:
If submodules in your project are empty you can run this command at root of your
project:
If you want to use SQLite to store session data persistently, build sqlite:
The cmake build flow is pretty straightforward:
In case you would also need to install the built library, check for more
information
- here.
+ here.
Build with the option
The shortest way to initiate the SDK is with this code snippet:
Here, you have to provide your appKey, and your Countly server URL. Please check
- here
+ here
for more information on how to acquire your application key (APP_KEY) and server
URL.
If you are in doubt about the correctness of your Countly SDK integration,
you can learn more about methods to verify it from
- here.
+ here.
- The first thing you should do while integrating our SDK is to enable logging.
- If logging is enabled, then our SDK will print out debug messages about its internal
- state and about encountered problems.
-
- Set
- All tracked information is tied to a "device ID", which is used as a unique identifier
- of your users.
-
- You have to specify the device ID by yourself (it has to be unique for each of
- your users). It may be an email or some other internal ID used in your system's
- internal logic.
-
In its unconfigured state, the SDK stores everything in memory.
@@ -140,7 +118,24 @@ make ./countly-tests # run unit test
To access the Countly Global Instance use the following code snippet:
+ The first thing you should do while integrating our SDK is to enable logging.
+ If logging is enabled, then our SDK will print out debug messages about its internal
+ state and about encountered problems.
+
+ Set
The Countly SDK for C++ can collect
@@ -151,33 +146,49 @@ make ./countly-tests # run unit test
You might catch an exception or similar error during your app’s runtime. You
may also log these handled exceptions to monitor how and when they are happening.
To log handled exceptions use the following code snippet:
Adding the SDK to the Project
-
+ git clone --recursive https://github.com/Countly/countly-sdk-cppgit clone --recursive https://github.com/Countly/countly-sdk-cpp
+ git submodule update --init --recursivegit submodule update --init --recursive# assuming we are on project root
+ # assuming we are on project root
cd vendor/sqlite
cmake -D BUILD_SHARED_LIBS=ON -B build . # out of source build, we don't like clutter :)
# we define `BUILD_SHARED_LIBS` because sqlite's cmake file compiles statically by default for some reason
@@ -50,14 +49,14 @@ make # you might want to add something like -j8 to parallelize the build process
project.
# assuming we are on project root again
+ # assuming we are on project root again
ccmake -B build . # this will launch a TUI, configure the build as you see fit
cd build
makeCOUNTLY_BUILD_TESTS and
@@ -67,9 +66,10 @@ makecmake -DCOUNTLY_BUILD_SAMPLE=ON -DCOUNTLY_BUILD_TESTS=ON -DCOUNTLY_USE_SQLITE=ON -B build . # or do it interactively with cmake
+
+make ./countly-tests # run unit test
+make ./countly-sample # run sample appcmake -DCOUNTLY_BUILD_SAMPLE=ON -DCOUNTLY_BUILD_TESTS=ON -DCOUNTLY_USE_SQLITE=ON -B build . # or do it interactively with cmake
cd build
-make ./countly-tests # run unit test
make ./countly-sample # run sample appSDK Integration
@@ -80,10 +80,12 @@ make ./countly-tests # run unit test
make ./countly-sample # run sample ap
+cly::Countly& countly = cly::Countly::getInstance();
countly.setDeviceID("test-device-id");
countly.start("YOUR_APP_KEY", "https://try.count.ly", 443, true);cly::Countly& countly = cly::Countly::getInstance();
+countly.setDeviceID("test-device-id");
+countly.start("YOUR_APP_KEY", "https://try.count.ly", 443, true);
make ./countly-sample # run sample ap
SDK Logging
-setLogger(logger_function) on the Counlty object
- to enable logging:
-
-void printLog(cly::Countly::LogLevel level, const string& msg) {...}
...
void (*logger_function)(cly::Countly::LogLevel level, const std::string& message);
logger_function = printLog;
cly::Countly::getInstance().setLogger(logger_function);Device ID
-
-cly::Countly::getInstance().setDeviceID("UNIQUE_DEVICE_ID");SDK Data Storage
make ./countly-sample # run sample ap
+cly::Countly::getInstance().
+cly::Countly::getInstance()SDK Logging
+setLogger(logger_function) on the Counlty object
+ to enable logging:
+void printLog(cly::Countly::LogLevel level, const string& msg) {...}
+...
+
+void (*logger_function)(cly::Countly::LogLevel level, const std::string& message);
+logger_function = printLog;
+cly::Countly::getInstance().setLogger(logger_function);
+Crash Reporting
make ./countly-sample # run sample ap
In the SDK all crash-related functionalities can be browsed from the returned
interface on:
+countly.crash().countly.crash()Handled Exceptions
+/*any additional info can be provided as a segmentation*/
std::map<std::string, std::string> segmentation = {
{"platform", "ubuntu"},
{"time", "60"},
};
/*should create the crashMetrics map*/
std::map<std::string, std::any> crashMetrics;
/*mandatory values*/
crashMetrics["_os"] = "Android"; # your OS info
crashMetrics["_app_version"] = "1.22";
/*any optional info*/
crashMetrics["_cpu"] = "armv7";
countly.crash().recordException("title", "stackTrace", true, crashMetrics, segmentation);/*any additional info can be provided as a segmentation*/
+std::map<std::string, std::string> segmentation = {
+ {"platform", "ubuntu"},
+ {"time", "60"},
+};
+
+/*should create the crashMetrics map*/
+std::map<std::string, std::any="std::any"> crashMetrics;
+
+/*mandatory values*/
+crashMetrics["_os"] = "Android"; # your OS info
+crashMetrics["_app_version"] = "1.22";
+
+/*any optional info*/
+crashMetrics["_cpu"] = "armv7";
+
+countly.crash().recordException("title", "stackTrace", true, crashMetrics, segmentation);recordException expects the parameters below:
-
@@ -191,7 +202,30 @@ make ./countly-tests # run unit testtitle - a string that describes the exception.
stackTrace - a string that describes the contents of the call
stack.
fatal - set true if the error is fatal.
crashMetrics - key/values contain device information e.g., app
version, OS.
segments - custom key/values to be reported.
make ./countly-sample # run sample ap
keys are optional, so you can add more key-value pairs to form a detailed crash
report from the available options shown below:
std::map<std::string, std::any> crashMetrics;
/*mandatory values*/
crashMetrics["_os"] = "Android"; /*your OS info*/
crashMetrics["_app_version"] = "22.06.1"; /*SDK version*/
/*optional values*/
crashMetrics["_os_version"] = "4.1";
crashMetrics["_manufacture"] = "Samsung"; /*may not be provided for ios or be constant, like Apple*/
crashMetrics["_device"] = "Galaxy S4"; /*model for Android, iPhone1,1 etc for iOS*/
crashMetrics["_resolution"] = "1900x1080"; /*SDK version*/
crashMetrics["_cpu"] = "armv7"; /*type of cpu used on device (for ios will be based on device)*/
crashMetrics["_opengl"] = "2.1"; /*version of open gl supported*/
crashMetrics["_ram_current"] = 1024; /*in megabytes*/
crashMetrics["_ram_total"] = 4096; /*in megabytes*/
crashMetrics["_disk_current"] = 3000; /*in megabytes*/
crashMetrics["_disk_total"] = 10240; /*in megabytes*/
crashMetrics["_bat"] = 99; /*battery level from 0 to 100*/
crashMetrics["_orientation"] = "portrait"; /*in which device was held, landscape, portrait, etc*/
crashMetrics["_root"] = false; /*true if device is rooted/jailbroken, false or not provided if not*/
crashMetrics["_online"] = false; /*true if device is connected to the internet (WiFi or 3G), false or not provided if not connected*/
crashMetrics["_muted"] = false; /*true if volume is off, device is in muted state*/
crashMetrics["_background"] = false; /*true if app was in background when it crashed*/
crashMetrics["_run"] = 2000; /*running time since app start in seconds*/
+std::map<std::string, std::any> crashMetrics;
+
+/*mandatory values*/
+crashMetrics["_os"] = "Android"; /*your OS info*/
+crashMetrics["_app_version"] = "22.06.1"; /*SDK version*/
+
+/*optional values*/
+crashMetrics["_os_version"] = "4.1";
+crashMetrics["_manufacture"] = "Samsung"; /*may not be provided for ios or be constant, like Apple*/
+crashMetrics["_device"] = "Galaxy S4"; /*model for Android, iPhone1,1 etc for iOS*/
+crashMetrics["_resolution"] = "1900x1080"; /*SDK version*/
+crashMetrics["_cpu"] = "armv7"; /*type of cpu used on device (for ios will be based on device)*/
+crashMetrics["_opengl"] = "2.1"; /*version of open gl supported*/
+crashMetrics["_ram_current"] = 1024; /*in megabytes*/
+crashMetrics["_ram_total"] = 4096; /*in megabytes*/
+crashMetrics["_disk_current"] = 3000; /*in megabytes*/
+crashMetrics["_disk_total"] = 10240; /*in megabytes*/
+crashMetrics["_bat"] = 99; /*battery level from 0 to 100*/
+crashMetrics["_orientation"] = "portrait"; /*in which device was held, landscape, portrait, etc*/
+crashMetrics["_root"] = false; /*true if device is rooted/jailbroken, false or not provided if not*/
+crashMetrics["_online"] = false; /*true if device is connected to the internet (WiFi or 3G), false or not provided if not connected*/
+crashMetrics["_muted"] = false; /*true if volume is off, device is in muted state*/
+crashMetrics["_background"] = false; /*true if app was in background when it crashed*/
+crashMetrics["_run"] = 2000; /*running time since app start in seconds*/
Throughout your app, you can leave crash breadcrumbs. They are short string logs
@@ -199,10 +233,10 @@ make ./countly-tests # run unit test
make ./countly-sample # run sample ap
the crash. After a crash happens, they will be sent together with the crash report.
The following command adds a crash breadcrumb:
-countly.crash().addBreadcrumb("breadcrumb");
+countly.crash().addBreadcrumb("breadcrumb");
- An event is any type of action that you can send to a Countly instance, e.g. purchases, changed settings, view enabled, and so on, letting you get valuable information about your application. + An event is any type of action that you can send to a Countly instance, e.g. purchases, changed settings, view enabled, and so on, letting you get valuable information about your application.
There are a couple of values that can be set when recording an event. The main
@@ -214,94 +248,120 @@ make ./countly-tests # run unit test
make ./countly-sample # run sample ap
Optionally there are also other properties that you might want to set:
std::string.
+ std::string.
- Here are some examples below, showing how to record an event for a purchase with varying levels of complexity: + Here are some examples below, showing how to record an event for a purchase with varying levels of complexity:
1. Event key and count
-cly::Countly::getInstance().RecordEvent("purchase", 1);
+cly::Countly::getInstance().RecordEvent("purchase", 1);
2. Event key, count, and sum
-cly::Countly::getInstance().RecordEvent("purchase", 1, 0.99);
+cly::Countly::getInstance().RecordEvent("purchase", 1, 0.99);
3. Event key and count with segmentation(s)
-std::map<std::string, std::string> segmentation;
segmentation["country"] = "Germany";
cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1);
+std::map<std::string, std::string> segmentation;
+segmentation["country"] = "Germany";
+
+cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1);
4. Event key, count, and sum with segmentation(s)
-std::map<std::string, std::string> segmentation;
segmentation["country"] = "Germany";
cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1, 0.99);
+std::map<std::string, std::string> segmentation;
+segmentation["country"] = "Germany";
+
+cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1, 0.99);
5. Event key, count, sum, and duration with segmentation(s)
-std::map<std::string, std::string> segmentation;
segmentation["country"] = "Germany";
cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1, 0.99, 60.0);
+std::map<std::string, std::string> segmentation;
+segmentation["country"] = "Germany";
+
+cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1, 0.99, 60.0);
- These are only a few examples of what you can do with Events. You may go beyond those examples and use country, app_version, game_level, time_of_day, or any other segmentation of your choice that will provide you with valuable insights. + These are only a few examples of what you can do with Events. You may go beyond those examples and use country, app_version, game_level, time_of_day, or any other segmentation of your choice that will provide you with valuable insights.
It's possible to create timed events by defining a start and a stop moment.
-cly::Event event("Some event", 1);
//start some event
event.startTimer();
//wait some time
//end the timer and record event
event.stopTimer();
-cly::Countly.getInstance().addEvent(event);
+cly::Event event("Some event", 1);
+
+//start some event
+event.startTimer();
+//wait some time
+
+//end the timer and record event
+event.stopTimer();
+
+cly::Countly.getInstance().addEvent(event);
You may also provide additional information e.g segmentation, count, and sum.
-//event with count and sum
-cly::Event event("Some event", 1, 0.99);
//add segmentation to event
event.addSegmentation("country", "Germany");
...
cly::Countly.getInstance().addEvent(event);
+//event with count and sum
+cly::Event event("Some event", 1, 0.99);
+
+//add segmentation to event
+event.addSegmentation("country", "Germany");
+
+...
+
+cly::Countly.getInstance().addEvent(event);
+
The SDK handles the sessions automatically. After calling the
- start(...) method, the SDK starts the session tracking automatically and extends sessions after every 60 seconds. This value is configurable during and after initialization.
Example:
+ start(...) method, the SDK starts the session tracking automatically and extends sessions after every 60 seconds. This value is configurable during and after initialization.
+ Example:
cly::Countly::getInstance().setAutomaticSessionUpdateInterval(10);
+cly::Countly::getInstance().setAutomaticSessionUpdateInterval(10);
- The SDK ends the current session whenever the user exits from the app. + The SDK ends the current session whenever the user exits from the app.
std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
+std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
While tracking views manually, you may add your custom segmentation to those views like this:
-std::map<std::string, std::string> segmentation = {
{"cats", "123"},
{"moons", "9.98"},
{"Moose", "deer"},
};
std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene", segmentation);
+std::map<std::string, std::string> segmentation = {
+ {"cats", "123"},
+ {"moons", "9.98"},
+ {"Moose", "deer"},
+};
+
+std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene", segmentation);
+
When the screen closes you can report it to the server by using one of the following methods: @@ -325,15 +392,17 @@ cly::Countly.getInstance().addEvent(event);
When you start recording a view by calling the - openView method, it returns a view ID of type std::string. You can use this ID to close a view. + openView method, it returns a view ID of type std::string. You can use this ID to close a view.
For example:
-std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
...
cly::Countly::getInstance().views().closeViewWithID(viewId);
+std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
+...
+cly::Countly::getInstance().views().closeViewWithID(viewId);
- 2. Ending a view with a view name:
You may close a view by
- its name using the following method:
+ 2. Ending a view with a view name:
+ You may close a view by its name using the following method:
cly::Countly::getInstance().views().closeViewWithName("Home Scene");
+cly::Countly::getInstance().views().closeViewWithName("Home Scene");
To review the resulting view data, go to the Analytics > Views
section in your Countly server. For more information on how to utilize view tracking
@@ -349,8 +418,11 @@ cly::Countly.getInstance().addEvent(event);
In the C++ SDK the device ID is not persistent and has to be provided every time - you start the SDK. + you start the SDK:
+cly::Countly::getInstance().setDeviceID("UNIQUE_DEVICE_ID");
+In case your application authenticates users, you might want to change the initial @@ -362,7 +434,7 @@ cly::Countly.getInstance().addEvent(event); with the current device ID will be transferred (merged) into the user profile with the device ID you specified in the following method call:
-cly::Countly::getInstance().setDeviceID("new-device-id", true);
+cly::Countly::getInstance().setDeviceID("new-device-id", true);
If you integrate this method, there might be times where you might want to track information about another user that starts using your app from the same device @@ -370,7 +442,7 @@ cly::Countly.getInstance().addEvent(event); the identity of the current user (user logs out). In those cases, you can change the current device ID to a new one without merging their data. You would call:
-cly::Countly::getInstance().setDeviceID("new-device-id", false);
+cly::Countly::getInstance().setDeviceID("new-device-id", false);
Doing it this , will prevent the previous user's data to merge with the new id.
@@ -390,12 +462,12 @@ cly::Countly.getInstance().addEvent(event); user base. There are 4 fields that can be provided:@@ -403,7 +475,14 @@ cly::Countly.getInstance().addEvent(event); info will be sent to the server at the start of the user session.
Example:
-string countryCode = "us";
string city = "Houston";
string latitude = "29.634933";
string longitude = "-95.220255";
string ipAddress = "192.168.0.1";
cly::Countly::getInstance().setLocation(countryCode, city, latitude + "," + longitude, ipAddress);
+string countryCode = "us";
+string city = "Houston";
+string latitude = "29.634933";
+string longitude = "-95.220255";
+string ipAddress = "192.168.0.1";
+
+cly::Countly::getInstance().setLocation(countryCode, city, latitude + "," + longitude, ipAddress);
+
Note that the IP address would only be updated if it's set during the init process.
@@ -427,14 +506,14 @@ cly::Countly.getInstance().addEvent(event);
To download Remote Config, call updateRemoteConfig().
cly::Countly.getInstance().updateRemoteConfig();
+cly::Countly.getInstance().updateRemoteConfig();
To access the stored config, call
cly::Countly.getInstance().getRemoteConfigValue(const std::string& key).
It will return null if there isn't any config stored.
cly::Countly.getInstance().getRemoteConfigValue("Key");
+cly::Countly.getInstance().getRemoteConfigValue("Key");
It returns a value of the type json.
+ If a property is set as an empty string, it will be deleted from the user on + the server side. +
The Countly C++ SDK allows you to upload user specific data to your Countly server. @@ -450,77 +533,104 @@ cly::Countly.getInstance().addEvent(event);
The keys for predefined user data fields are as follows:
| Key | -Type | -Description | -
|---|---|---|
| name | -string | -User's full name | -
| username | -string | -User's nickname | -
| string | -User's email address | -|
| organization | -string | -User's organization name | -
| phone | -string | -User's phone number | -
| picture | -string | -URL to avatar or profile picture of the user | -
| gender | -string | -User's gender as M for male and F for female | -
| byear | -string | -User's year of birth as integer | -
| Key | +Type | +Description | +
|---|---|---|
| name | +string | +User's full name | +
| username | +string | +User's nickname | +
| string | +User's email address | +|
| organization | +string | +User's organization name | +
| phone | +string | +User's phone number | +
| picture | +string | +URL to avatar or profile picture of the user | +
| gender | +string | +User's gender as M for male and F for female | +
| byear | +string | +User's year of birth as integer | +
The SDK allows you to upload user details using the methods listed below.
Example:
-std::map<std::string, std::string> userdetail = {
{"name", "Full name"},
{"username", "username123"},
{"email", "useremail@email.com"},
{"phone", "222-222-222"},
{"picture", "http://webresizer.com/images2/bird1_after.jpg"},
{"gender", "M"},
{"byear", "1991"},
{"organization", "Organization"},
};
cly::Countly.getInstance().setUserDetails(userdetail);
+std::map<std::string, std::string> userdetail = {
+ {"name", "Full name"},
+ {"username", "username123"},
+ {"email", "useremail@email.com"},
+ {"phone", "222-222-222"},
+ {"picture", "http://webresizer.com/images2/bird1_after.jpg"},
+ {"gender", "M"},
+ {"byear", "1991"},
+ {"organization", "Organization"},
+};
+cly::Countly.getInstance().setUserDetails(userdetail);
+
The SDK gives you the flexibility to send only the custom data to Countly servers, even when you don’t want to send other user-related data.
Example:
-std::map<std::string, std::string> userdetail = {
{"Height", "5.8"},
{"Mole", "Lower Left Cheek"}
};
cly::Countly.getInstance().setCustomUserDetails(userdetail);
+std::map<std::string, std::string> userdetail = {
+ {"Height", "5.8"},
+ {"Mole", "Lower Left Cheek"}
+};
+
+cly::Countly.getInstance().setCustomUserDetails(userdetail);
+
The SDK allows you to set the user's picture URL along with other details using the methods listed below.
Example:
-std::map<std::string, std::string> userdetail = {
{"name", "Full name"},
{"picture", "http://webresizer.com/images2/bird1_after.jpg"},
};
cly::Counlty.getInstance().setUserDetails(userdetail);
+std::map<std::string, std::string> userdetail = {
+ {"name", "Full name"},
+ {"picture", "http://webresizer.com/images2/bird1_after.jpg"},
+};
+
+cly::Counlty.getInstance().setUserDetails(userdetail);
+
You may set an optional salt to be used for calculating the checksum
of requested data which will be sent with each request, using the
@@ -529,8 +639,23 @@ cly::Countly.getInstance().addEvent(event);
server is set, all requests would be checked for the validity of the
&checksum the field before being processed.
cly::Countly.getInstance().setSalt("salt");
+cly::Countly.getInstance().setSalt("salt");
+ These are the methods that lets you configure the Countly SDK: +
++ example_integration.cpp + covers basic functionalities. +
Before or after SDK starts, you can set a threshold for the number of events
@@ -538,7 +663,7 @@ cly::Countly.getInstance().addEvent(event);
are all sent to the request queue.
Example:
cly::Counlty.getInstance().setEventsToRQThreshold(10);
+cly::Counlty.getInstance().setEventsToRQThreshold(10);
When the threshold is reached, the SDK batches all the events in the event queue
and sends them to the request queue to be sent to the server in a single request.
@@ -555,7 +680,7 @@ cly::Countly.getInstance().addEvent(event);
(100 by default) and starts again at the next iteration of the update loop.
Example:
cly::Counlty.getInstance().setMaxRQProcessingBatchSize(10);
+cly::Counlty.getInstance().setMaxRQProcessingBatchSize(10);
In case you need persistent storage, you would need to build the SDK with that @@ -567,13 +692,28 @@ cly::Countly.getInstance().addEvent(event); file could be stored.
cly::Countly::getInstance().SetPath("databaseFileName.db");
+ cly::Countly::getInstance().SetPath("databaseFileName.db");
Building your SDK with SQLite would enable event and request queues to be stored persistently in your device. SDK would also try to rebuild the database file, repacking it into a minimal amount of disk space (SQLite Vacuum) every initialization.
++ User metrics information is sent to the server with every 'begin session' request + and when requesting remote config. It's possible to override this information + and provide custom metrics. For such cases, the SetMetrics() method can be used + before starting the SDK. User metrics that are provided by SetMetrics() method + are, _os, _os_version, _device, _resolution, _carrier, and _app_version respectively. + For further information about these parameters, please refer to + here. +
+The example usage of SetMetrics() would be like this:
+Countly &ct = Countly::getInstance();
+// OS, OS version, device, resolution, carrier, app version
+ct.SetMetrics("Windows 10", "10.22", "Lenovo", "800x600", "Carrier", "1.0");
+ct.start(_appKey, _serverUrl, 443, true);
C++ SDK allows users to set a custom SHA-256 method for calculating the checksum
@@ -593,28 +733,41 @@ cly::Countly.getInstance().addEvent(event);
2. Set custom SHA-256 method setSha256
For example:
--std::string customChecksumCalculator(const std::string& data) {
...
return result;
}cly::Countly& countly = cly::Countly.getInstance();countly.setSalt("salt");
countly.setSha256(customChecksumCalculator);
std::string customChecksumCalculator(const std::string& data) {
+ ...
+ return result;
+}
+
+
+cly::Countly& countly = cly::Countly.getInstance();
+
+countly.setSalt("salt");
+countly.setSha256(customChecksumCalculator);
+
+
In some cases your project might need to install Countly globally one the system.
In those situation you would also want to run the make installcommand.
As per the description, it install the countly library on the system.
For example:
-#configure the SDK build
+#configure the SDK build
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local -DBUILD_SHARED_LIBS=OFF -B build
-
cd build
#build the SDK
+
+cd build
+#build the SDK
make
-
#install countly on the system
+
+#install countly on the system
make install
- -
+
-
CMAKE_INSTALL_PREFIX
Install directory used by install. If “make install” is invoked or INSTALL
is built, this directory is prepended onto all install directories. This
variable defaults to '/usr/local' on UNIX and 'c:/Program Files' on Windows.
- -
+
-
BUILD_SHARED_LIBS
If present and true, this will cause all libraries to be built shared unless
the library was explicitly added as a static library.
@@ -625,11 +778,6 @@ make install
There are some data that is collected by SDK to perform their functions and implement the required features. Before any of it is sent to the server, it is stored locally. -
-
- * When sending any network requests to the server, the following things are sent
- in addition to the main data:
- - Timestamp of when the request is created
- - SDK version
- - SDK name
+ Here
+ is the collected data for the SDK.
First, clone the repository with its submodules:
git clone --recursive https://github.com/Countly/countly-sdk-cpp
+ git clone --recursive https://github.com/Countly/countly-sdk-cpp
If submodules in your project are empty you can run this command at root of your project:
git submodule update --init --recursive
+ git submodule update --init --recursive
If you want to use SQLite to store session data persistently, build sqlite:
# assuming we are on project root
+ # assuming we are on project root
cd vendor/sqlite
cmake -D BUILD_SHARED_LIBS=ON -B build . # out of source build, we don't like clutter :)
# we define `BUILD_SHARED_LIBS` because sqlite's cmake file compiles statically by default for some reason
@@ -49,7 +49,7 @@ make # you might want to add something like -j8 to parallelize the build process
The cmake build flow is pretty straightforward:
# assuming we are on project root again
+ # assuming we are on project root again
ccmake -B build . # this will launch a TUI, configure the build as you see fit
cd build
make
@@ -66,7 +66,7 @@ make
project.
cmake -DCOUNTLY_BUILD_SAMPLE=ON -DCOUNTLY_BUILD_TESTS=ON -DCOUNTLY_USE_SQLITE=ON -B build . # or do it interactively with cmake
+ cmake -DCOUNTLY_BUILD_SAMPLE=ON -DCOUNTLY_BUILD_TESTS=ON -DCOUNTLY_USE_SQLITE=ON -B build . # or do it interactively with cmake
cd build
make ./countly-tests # run unit test
make ./countly-sample # run sample app
@@ -80,7 +80,7 @@ make ./countly-sample # run sample app
The shortest way to initiate the SDK is with this code snippet:
-cly::Countly& countly = cly::Countly::getInstance();
+cly::Countly& countly = cly::Countly::getInstance();
countly.setDeviceID("test-device-id");
countly.start("YOUR_APP_KEY", "https://try.count.ly", 443, true);
@@ -118,7 +118,7 @@ countly.start("YOUR_APP_KEY", "https://try.count.ly", 443, true);
To access the Countly Global Instance use the following code snippet:
-cly::Countly::getInstance()
+cly::Countly::getInstance()
The first thing you should do while integrating our SDK is to enable logging.
@@ -129,7 +129,7 @@ countly.start("YOUR_APP_KEY", "https://try.count.ly", 443, true);
Set setLogger(logger_function) on the Counlty object
to enable logging:
void printLog(cly::Countly::LogLevel level, const string& msg) {...}
+void printLog(cly::Countly::LogLevel level, const string& msg) {...}
...
void (*logger_function)(cly::Countly::LogLevel level, const std::string& message);
@@ -146,14 +146,14 @@ cly::Countly::getInstance().setLogger(logger_function);
In the SDK all crash-related functionalities can be browsed from the returned
interface on:
-countly.crash()
+countly.crash()
Handled Exceptions
You might catch an exception or similar error during your app’s runtime. You
may also log these handled exceptions to monitor how and when they are happening.
To log handled exceptions use the following code snippet:
-/*any additional info can be provided as a segmentation*/
+/*any additional info can be provided as a segmentation*/
std::map<std::string, std::string> segmentation = {
{"platform", "ubuntu"},
{"time", "60"},
@@ -174,21 +174,21 @@ countly.crash().recordException("title", "stackTrace", true, crashMetrics, segme
recordException expects the parameters below:
- -
+
-
title - a string that describes the exception.
- -
+
-
stackTrace - a string that describes the contents of the call
stack.
- -
+
-
fatal - set true if the error is fatal.
- -
+
-
crashMetrics - key/values contain device information e.g., app
version, OS.
- -
+
-
segments - custom key/values to be reported.
@@ -202,7 +202,7 @@ countly.crash().recordException("title", "stackTrace", true, crashMetrics, segme
keys are optional, so you can add more key-value pairs to form a detailed crash
report from the available options shown below:
-std::map<std::string, std::any> crashMetrics;
+std::map<std::string, std::any> crashMetrics;
/*mandatory values*/
crashMetrics["_os"] = "Android"; /*your OS info*/
@@ -233,10 +233,10 @@ crashMetrics["_run"] = 2000; /*running time since app start in seconds*/<
the crash. After a crash happens, they will be sent together with the crash report.
The following command adds a crash breadcrumb:
-countly.crash().addBreadcrumb("breadcrumb");
+countly.crash().addBreadcrumb("breadcrumb");
Events
- An event is any type of action that you can send to a Countly instance, e.g. purchases, changed settings, view enabled, and so on, letting you get valuable information about your application.
+ An event is any type of action that you can send to a Countly instance, e.g. purchases, changed settings, view enabled, and so on, letting you get valuable information about your application.
There are a couple of values that can be set when recording an event. The main
@@ -248,88 +248,88 @@ crashMetrics["_run"] = 2000; /*running time since app start in seconds*/
<
Optionally there are also other properties that you might want to set:
- -
+
-
count - a whole numerical value that marks how many times
this event has happened. The default value for this is 1.
- -
+
-
sum - This value would be summed across all events in the
dashboard. For example, for in-app purchase events, it can be the sum of
purchased items. Its default value is 0.
- -
+
-
duration - For recording and tracking the duration of events.
The default value is 0.
- -
+
-
segments - A value where you can provide custom segmentation
for your events to track additional information. It is a key and value map.
The accepted data type for the value is
-
std::string.
+ std::string.
Recording Events
- Here are some examples below, showing how to record an event for a purchase with varying levels of complexity:
+ Here are some examples below, showing how to record an event for a purchase with varying levels of complexity:
- -
+
-
Usage 1: Times the purchase event occurred.
- -
+
-
Usage 2: Times the purchase event occurred + the total amount
of those purchases.
- -
+
-
Usage 3: Times the purchase event occurred +
- origin of the purchase.
+ origin of the purchase.
- -
+
-
Usage 4: Times the purchase event occurred +
- the total amount + origin of the purchase.
+ the total amount + origin of the purchase.
- -
+
-
Usage 5: Times the purchase event occurred +
- the total amount + origin of the purchase + the total duration of those events.
+ the total amount + origin of the purchase + the total duration of those events.
1. Event key and count
-cly::Countly::getInstance().RecordEvent("purchase", 1);
+cly::Countly::getInstance().RecordEvent("purchase", 1);
2. Event key, count, and sum
-cly::Countly::getInstance().RecordEvent("purchase", 1, 0.99);
+cly::Countly::getInstance().RecordEvent("purchase", 1, 0.99);
3. Event key and count with segmentation(s)
-std::map<std::string, std::string> segmentation;
+std::map<std::string, std::string> segmentation;
segmentation["country"] = "Germany";
cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1);
4. Event key, count, and sum with segmentation(s)
-std::map<std::string, std::string> segmentation;
+std::map<std::string, std::string> segmentation;
segmentation["country"] = "Germany";
cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1, 0.99);
5. Event key, count, sum, and duration with segmentation(s)
-std::map<std::string, std::string> segmentation;
+std::map<std::string, std::string> segmentation;
segmentation["country"] = "Germany";
cly::Countly::getInstance().RecordEvent("purchase", segmentation, 1, 0.99, 60.0);
- These are only a few examples of what you can do with Events. You may go beyond those examples and use country, app_version, game_level, time_of_day, or any other segmentation of your choice that will provide you with valuable insights.
+ These are only a few examples of what you can do with Events. You may go beyond those examples and use country, app_version, game_level, time_of_day, or any other segmentation of your choice that will provide you with valuable insights.
Timed Events
It's possible to create timed events by defining a start and a stop moment.
-cly::Event event("Some event", 1);
+cly::Event event("Some event", 1);
//start some event
event.startTimer();
@@ -342,7 +342,7 @@ cly::Countly.getInstance().addEvent(event);
You may also provide additional information e.g segmentation, count, and sum.
-//event with count and sum
+//event with count and sum
cly::Event event("Some event", 1, 0.99);
//add segmentation to event
@@ -356,11 +356,12 @@ cly::Countly.getInstance().addEvent(event);
Automatic Session Tracking
The SDK handles the sessions automatically. After calling the
- start(...) method, the SDK starts the session tracking automatically and extends sessions after every 60 seconds. This value is configurable during and after initialization.
Example:
+ start(...) method, the SDK starts the session tracking automatically and extends sessions after every 60 seconds. This value is configurable during and after initialization.
+ Example:
-cly::Countly::getInstance().setAutomaticSessionUpdateInterval(10);
+cly::Countly::getInstance().setAutomaticSessionUpdateInterval(10);
- The SDK ends the current session whenever the user exits from the app.
+ The SDK ends the current session whenever the user exits from the app.
View Tracking
Manual View Recording
@@ -369,12 +370,12 @@ cly::Countly.getInstance().addEvent(event);
report which views a user has visited with the duration of that visit. To report
a screen from your app to the Countly server, you can use the following method:
-std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
+std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
While tracking views manually, you may add your custom segmentation to those
views like this:
-std::map<std::string, std::string> segmentation = {
+std::map<std::string, std::string> segmentation = {
{"cats", "123"},
{"moons", "9.98"},
{"Moose", "deer"},
@@ -391,17 +392,17 @@ std::string& viewID = cly::Countly::getInstance().views().openView("Home Sce
When you start recording a view by calling the
- openView method, it returns a view ID of type std::string. You can use this ID to close a view.
+ openView method, it returns a view ID of type std::string. You can use this ID to close a view.
For example:
-std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
+std::string& viewID = cly::Countly::getInstance().views().openView("Home Scene");
...
cly::Countly::getInstance().views().closeViewWithID(viewId);
- 2. Ending a view with a view name:
You may close a view by
- its name using the following method:
+ 2. Ending a view with a view name:
+ You may close a view by its name using the following method:
-cly::Countly::getInstance().views().closeViewWithName("Home Scene");
+cly::Countly::getInstance().views().closeViewWithName("Home Scene");
To review the resulting view data, go to the Analytics > Views
section in your Countly server. For more information on how to utilize view tracking
@@ -420,7 +421,7 @@ cly::Countly::getInstance().views().closeViewWithID(viewId);
you start the SDK:
- cly::Countly::getInstance().setDeviceID("UNIQUE_DEVICE_ID");
+ cly::Countly::getInstance().setDeviceID("UNIQUE_DEVICE_ID");
Changing Device ID
@@ -433,7 +434,7 @@ cly::Countly::getInstance().views().closeViewWithID(viewId);
with the current device ID will be transferred (merged) into the user profile
with the device ID you specified in the following method call:
-cly::Countly::getInstance().setDeviceID("new-device-id", true);
+cly::Countly::getInstance().setDeviceID("new-device-id", true);
If you integrate this method, there might be times where you might want to track
information about another user that starts using your app from the same device
@@ -441,7 +442,7 @@ cly::Countly::getInstance().views().closeViewWithID(viewId);
the identity of the current user (user logs out). In those cases, you can change
the current device ID to a new one without merging their data. You would call:
-cly::Countly::getInstance().setDeviceID("new-device-id", false);
+cly::Countly::getInstance().setDeviceID("new-device-id", false);
Doing it this , will prevent the previous user's data to merge with the new id.
@@ -461,12 +462,12 @@ cly::Countly::getInstance().views().closeViewWithID(viewId);
user base. There are 4 fields that can be provided:
- - Country code (two-letter ISO standard).
- - City name (must be set together with the country code).
- -
+
- Country code (two-letter ISO standard).
+ - City name (must be set together with the country code).
+ -
Latitude and longitude values, separated by a comma e.g. "56.42345,123.45325".
- - Your user’s IP address.
+ - Your user’s IP address.
Setting Location
@@ -474,7 +475,7 @@ cly::Countly::getInstance().views().closeViewWithID(viewId);
info will be sent to the server at the start of the user session.
Example:
-string countryCode = "us";
+string countryCode = "us";
string city = "Houston";
string latitude = "29.634933";
string longitude = "-95.220255";
@@ -505,14 +506,14 @@ cly::Countly::getInstance().setLocation(countryCode, city, latitude + "," + long
To download Remote Config, call updateRemoteConfig().
-cly::Countly.getInstance().updateRemoteConfig();
+cly::Countly.getInstance().updateRemoteConfig();
Accessing Remote Config Values
To access the stored config, call
cly::Countly.getInstance().getRemoteConfigValue(const std::string& key).
It will return null if there isn't any config stored.
-cly::Countly.getInstance().getRemoteConfigValue("Key");
+cly::Countly.getInstance().getRemoteConfigValue("Key");
It returns a value of the type json.
@@ -522,7 +523,8 @@ cly::Countly::getInstance().setLocation(countryCode, city, latitude + "," + long
this documentation.
- If a property is set as an empty string, it will be deleted from the user on the server side.
+ If a property is set as an empty string, it will be deleted from the user on
+ the server side.
Setting Predefined Values
@@ -531,61 +533,65 @@ cly::Countly::getInstance().setLocation(countryCode, city, latitude + "," + long
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 organization name
-
-
- phone
- string
- User's phone number
-
-
- picture
- string
- URL to avatar or profile picture of the user
-
-
- gender
- string
- User's gender as M for male and F for female
-
-
- byear
- string
- User's year of birth as integer
-
-
-
+
+
+
+
+ Key
+ Type
+ Description
+
+
+
+
+ name
+ string
+ User's full name
+
+
+ username
+ string
+ User's nickname
+
+
+ email
+ string
+ User's email address
+
+
+ organization
+ string
+ User's organization name
+
+
+ phone
+ string
+ User's phone number
+
+
+ picture
+ string
+ URL to avatar or profile picture of the user
+
+
+ gender
+ string
+ User's gender as M for male and F for female
+
+
+ byear
+ string
+ User's year of birth as integer
+
+
+
+
The SDK allows you to upload user details using the methods listed below.
Example:
-std::map<std::string, std::string> userdetail = {
+std::map<std::string, std::string> userdetail = {
{"name", "Full name"},
{"username", "username123"},
{"email", "useremail@email.com"},
@@ -603,7 +609,7 @@ cly::Countly.getInstance().setUserDetails(userdetail);
even when you don’t want to send other user-related data.
Example:
-std::map<std::string, std::string> userdetail = {
+std::map<std::string, std::string> userdetail = {
{"Height", "5.8"},
{"Mole", "Lower Left Cheek"}
};
@@ -616,7 +622,7 @@ cly::Countly.getInstance().setCustomUserDetails(userdetail);
the methods listed below.
Example:
-std::map<std::string, std::string> userdetail = {
+std::map<std::string, std::string> userdetail = {
{"name", "Full name"},
{"picture", "http://webresizer.com/images2/bird1_after.jpg"},
};
@@ -624,7 +630,7 @@ cly::Countly.getInstance().setCustomUserDetails(userdetail);
cly::Counlty.getInstance().setUserDetails(userdetail);
Security and Privacy
-Parameter Tamper Protection
+Parameter Tamper Protection
You may set an optional salt to be used for calculating the checksum
of requested data which will be sent with each request, using the
@@ -633,8 +639,18 @@ cly::Counlty.getInstance().setUserDetails(userdetail);
server is set, all requests would be checked for the validity of the
&checksum the field before being processed.
-cly::Countly.getInstance().setSalt("salt");
+cly::Countly.getInstance().setSalt("salt");
Other Features and Notes
+SDK Config Parameters Explained
+
+ These are the methods that lets you configure the Countly SDK:
+
+
+ -
+ disableAutoEventsOnUserProperties() - Disables sending events
+ on user property calls.
+
+
Example Integrations
example_integration.cpp
@@ -647,7 +663,7 @@ cly::Counlty.getInstance().setUserDetails(userdetail);
are all sent to the request queue.
Example:
-cly::Counlty.getInstance().setEventsToRQThreshold(10);
+cly::Counlty.getInstance().setEventsToRQThreshold(10);
When the threshold is reached, the SDK batches all the events in the event queue
and sends them to the request queue to be sent to the server in a single request.
@@ -664,7 +680,7 @@ cly::Counlty.getInstance().setUserDetails(userdetail);
(100 by default) and starts again at the next iteration of the update loop.
Example:
-cly::Counlty.getInstance().setMaxRQProcessingBatchSize(10);
+cly::Counlty.getInstance().setMaxRQProcessingBatchSize(10);
Setting Up SQLite Storage
In case you need persistent storage, you would need to build the SDK with that
@@ -676,7 +692,7 @@ cly::Counlty.getInstance().setUserDetails(userdetail);
file could be stored.
- cly::Countly::getInstance().SetPath("databaseFileName.db");
+ cly::Countly::getInstance().SetPath("databaseFileName.db");
Building your SDK with SQLite would enable event and request queues to be stored
@@ -694,7 +710,7 @@ cly::Counlty.getInstance().setUserDetails(userdetail);
here.
The example usage of SetMetrics() would be like this:
-Countly &ct = Countly::getInstance();
+Countly &ct = Countly::getInstance();
// OS, OS version, device, resolution, carrier, app version
ct.SetMetrics("Windows 10", "10.22", "Lenovo", "800x600", "Carrier", "1.0");
ct.start(_appKey, _serverUrl, 443, true);
@@ -717,7 +733,7 @@ ct.start(_appKey, _serverUrl, 443, true);
2. Set custom SHA-256 method setSha256
For example:
-std::string customChecksumCalculator(const std::string& data) {
+std::string customChecksumCalculator(const std::string& data) {
...
return result;
}
@@ -728,27 +744,30 @@ cly::Countly& countly = cly::Countly.getInstance();
countly.setSalt("salt");
countly.setSha256(customChecksumCalculator);
-Additional project install option
+Additional project install option
In some cases your project might need to install Countly globally one the system.
In those situation you would also want to run the make installcommand.
As per the description, it install the countly library on the system.
For example:
-#configure the SDK build
+#configure the SDK build
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local -DBUILD_SHARED_LIBS=OFF -B build
-
cd build
#build the SDK
+
+cd build
+#build the SDK
make
-
#install countly on the system
+
+#install countly on the system
make install
- -
+
-
CMAKE_INSTALL_PREFIX
Install directory used by install. If “make install” is invoked or INSTALL
is built, this directory is prepended onto all install directories. This
variable defaults to '/usr/local' on UNIX and 'c:/Program Files' on Windows.
- -
+
-
BUILD_SHARED_LIBS
If present and true, this will cause all libraries to be built shared unless
the library was explicitly added as a static library.
@@ -761,4 +780,4 @@ make install
the required features. Before any of it is sent to the server, it is stored locally.
Here
is the collected data for the SDK.
-
+
\ No newline at end of file
diff --git a/java/current.md b/java/current.md
index 2b41a9a8..bc33fca4 100644
--- a/java/current.md
+++ b/java/current.md
@@ -26,20 +26,20 @@
To add it, you first have to add the MavenCentral repository. For Gradle you
would do it something like this:
-buildscript {
+buildscript {
repositories {
mavenCentral()
}
-}
+}
The dependency can be added as:
-dependencies {
- implementation "ly.count.sdk:java:24.1.3"
-}
+dependencies {
+ implementation "ly.count.sdk:java:24.1.4"
+}
Or as:
-<dependency>
+<dependency>
<groupId>ly.count.sdk</groupId>
<artifactId>java</artifactId>
- <version>24.1.3</version>
+ <version>24.1.4</version>
<type>pom</type>
</dependency>
SDK Integration
@@ -50,7 +50,7 @@
The shortest way to initiate the SDK is with this code snippet:
-File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
+File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
.setLoggingLevel(Config.LoggingLevel.DEBUG)
@@ -100,10 +100,10 @@ Countly.instance().init(config);
to the console.
- Set setLoggingLevel on the config object to enable
- logging:
+ Set setLoggingLevel on the config object to
+ enable logging:
-File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
+File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
.setLoggingLevel(Config.LoggingLevel.DEBUG);
@@ -113,9 +113,11 @@ Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolde
Log Listener
- To listen to the SDK's internal logs, you can call setLogListener on the Config Object. If set, SDK will forward its internal logs to this listener regardless of SDK's loggingLevel .
+ To listen to the SDK's internal logs, you can call setLogListener
+ on the Config Object. If set, SDK will forward its internal logs
+ to this listener regardless of SDK's loggingLevel .
-config.setLogListener((logMessage, logLevel) -> {
+config.setLogListener((logMessage, logLevel) -> {
//print log
});
Crash Reporting
@@ -126,25 +128,27 @@ Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolde
exceptions by default if the consent for crash reporting is given. You can reach
all crash-related functionality from the returned interface on:
-Countly.instance().crashes()
+Countly.instance().crashes()
Automatic Crash Handling
Automatic crash handling is enabled by default. To disable it call this method
on the config object during initialization:
-config.disableUnhandledCrashReporting();
+config.disableUnhandledCrashReporting();
Handled Exceptions
- You might catch an exception or similar error during your app’s runtime. To report them use the following method:
+ You might catch an exception or similar error during your app’s runtime. To report
+ them use the following method:
-Countly.instance().crashes().recordHandledException(Throwable t);
+Countly.instance().crashes().recordHandledException(Throwable t);
// Or you can also add segment to be recorded with the error
Countly.instance().crashes().recordHandledException(Throwable t, Map<String, Object> segment);
- If you have handled an exception and it turns out to be fatal to your app, you may use this call:
+ If you have handled an exception and it turns out to be fatal to your app, you
+ may use this call:
-Countly.instance().crashes().recordUnhandledException(Throwable t);
+Countly.instance().crashes().recordUnhandledException(Throwable t);
// Or you can also add segment to be recorded with the error
Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String, Object> segment);
@@ -155,12 +159,12 @@ Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String
will be sent together with the crash report.
Following command adds crash breadcrumb:
-Countly.instance().crashes().addCrashBreadcrumb(String record);
+Countly.instance().crashes().addCrashBreadcrumb(String record);
The maximum breadcrumb limit is 100. To change the maximum limit use this method
during initialization:
-config.setMaxBreadcrumbCount(int maxBreadcrumbCount);
+config.setMaxBreadcrumbCount(int maxBreadcrumbCount);
Events
Events
@@ -173,23 +177,23 @@ Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String
An Event object contains the following data types:
- -
+
-
name, or event key. Required. A unique string that identifies
the event.
- -
+
-
count - number of times. Required, 1 by default. Like a number
of goods added to the shopping basket.
- -
+
-
sum - sum of something, amount. Optional. Like a total sum of
the basket.
- -
+
-
dur - duration of the event. Optional. For example how much
time users spent checking out.
- -
+
-
segmentation - some data associated with the event. Optional.
It's a Map<String, Object> which can be filled with arbitrary data
like {"category": "Pants", "size": "M"}. The valid data types for segmentation
@@ -203,7 +207,7 @@ Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String
events interface:
- Map<String, Object> segmentation = new HashMap<String, Object>();
+ Map<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("Time Spent", 60);
segmentation.put("Retry Attempts", 60);
@@ -215,43 +219,45 @@ Countly.instance().events().recordEvent("purchase", segmentation, 2, 19.98, 35);
until one of the following happens:
- -
+
-
Config.sendUpdateEachSeconds seconds passed since begin or last
update request in case of automatic session control.
- -
+
-
Config.eventsBufferSize events have been already recorded and
not sent yet.
- -
+
-
Session.update() have been called by the developer.
- -
+
-
Session.end() have been called by the developer or by Countly
SDK in case of automatic session control.
- We have provided an example of recording a purchase event below. Here is a quick summary of the information with which each usage will provide us:
+ We have provided an example of recording a purchase event below.
+ Here is a quick summary of the information with which each usage will provide
+ us:
- -
+
-
Usage 1: how many times the purchase event occurred.
- -
+
-
Usage 2: how many times the purchase event occurred + the
total amount of those purchases.
- -
+
-
Usage 3: how many times the purchase event occurred + from
which countries and application versions those purchases were made.
- -
+
-
Usage 4: how many times the purchase event occurred + the
total amount, both of which are also available, segmented into countries
and application versions.
- -
+
-
Usage 5: how many times the purchase event occurred + the
total amount, both of which are also available, segmented into countries
and application versions + the total duration of those events.
@@ -260,15 +266,15 @@ Countly.instance().events().recordEvent("purchase", segmentation, 2, 19.98, 35);
1. Event key and count
-Countly.instance().events().recordEvent("purchase", 1);
+Countly.instance().events().recordEvent("purchase", 1);
2. Event key, count, and sum
-Countly.instance().events().recordEvent("purchase", 1, 20.3);
+Countly.instance().events().recordEvent("purchase", 1, 20.3);
3. Event key and count with segmentation(s)
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("country", "Germany");
segmentation.put("app_version", 1.0);
@@ -276,21 +282,25 @@ Countly.instance().events().recordEvent("purchase", segmentation, 1);
4. Event key, count, and sum with segmentation(s)
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("country", "Germany");
segmentation.put("app_version", 1.0);
-Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5);
+Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5);
+
5. Event key, count, sum, and duration with segmentation(s)
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("country", "Germany");
segmentation.put("app_version", 1.0);
-Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5, 5.3);
+Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5, 5.3);
+
- Those are only a few examples of what you can do with events. You may extend those examples and use Country, app_version, game_level, time_of_day, and any other segmentation that will provide you with valuable insights.
+ Those are only a few examples of what you can do with events. You may extend
+ those examples and use Country, app_version, game_level, time_of_day, and any
+ other segmentation that will provide you with valuable insights.
Timed Events
@@ -300,39 +310,39 @@ Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5, 5.3);
The basic use case for timed events is following:
- -
+
-
User starts playing a level "37" of your game, you call
Countly.instance().events().startEvent("LevelTime") to start
tracking how much time a user spends on this level. Also keep your segmentation
values in a map like
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("level", 37);
- -
+
-
Then, something happens when the user is at that level; for example, the user buys some coins. Along with the regular "Purchase" event, you decide you want to segment the "LevelTime" event with purchase information. While ending the event, you also pass the sum value as the purchase amount to the function call.
- -
+
-
Once the user stops playing, you need to stop this event and call:
Countly.instance().events().endEvent("LevelTime",segmentation, 1, 9.99)
- -
+
-
If you decide to cancel the event you can call:
Countly.instance().events().cancelEvent("LevelTime")
Once this event is sent to the server, you'll see:
- -
+
-
how much time users spend on each level (duration per
level
segmentation);
- -
+
-
which levels are generating the most revenue (sum per
level
segmentation);
- -
+
-
which levels are not generating revenue at all since you don't show ad there
(0 sums in
level segmentation).
@@ -356,18 +366,18 @@ segmentation.put("level", 37);
Session lifecycle methods include:
- -
+
-
session.begin() must be called when you want to send begin session
request to the server. This request contains all device metrics: device,
model, carrier, etc.
- -
+
-
session.update() can be called to send a session duration update
to the server along with any events, user properties, and any other data
types supported by Countly SDK. Called each Config.sendUpdateEachSeconds
seconds automatically. It can also be called more often manually.
- -
+
-
session.end() must be called to mark the end of the session.
All the data recorded since the last session.update() or since
session.begin() in case no updates have been sent yet, is sent
@@ -378,10 +388,10 @@ segmentation.put("level", 37);
You can track the views of your application with the Java SDK. With views feature,
you can also create
- flows
+ flows
to see view transitions. Public interface of the views can be accessed via:
-Countly.instance().views()
+Countly.instance().views()
Manual View Reporting
@@ -392,9 +402,11 @@ segmentation.put("level", 37);
Auto Stopped Views
- An easy way to track views is with using the auto stopped views. These views would stop if another view starts. You can start an auto stopped view with or without segmentation like this:
+ An easy way to track views is with using the auto stopped views. These views
+ would stop if another view starts. You can start an auto stopped view with or
+ without segmentation like this:
-// without segmentation, use view id for further manipulation of views
+// without segmentation, use view id for further manipulation of views
String viewID = Countly.instance().views().startAutoStoppedView("View Name");
// Or with segmentation
@@ -407,17 +419,18 @@ viewSegmentation.put("Moose", "Deer");
String view2ID = Countly.instance().views().startAutoStoppedView("View Name", viewSegmentation);
Regular Views
- As opposed to the "auto stopped views", with regular views you can have multiple of them started at the same time, and then you can control them independently.
+ As opposed to the "auto stopped views", with regular views you can have multiple
+ of them started at the same time, and then you can control them independently.
You can start a view that would not close when another view starts, like this:
-Countly.instance().views().startView("View Name");
+Countly.instance().views().startView("View Name");
While manually tracking views, you may add your custom segmentation to them like
this:
-Map<String, Object> viewSegmentation = new HashMap<>();
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
@@ -426,29 +439,28 @@ Countly.instance().views().startView("View Name", viewSegmentation);
These views would also return a string view ID when they are called.
-Stopping Views
+Stopping Views
You can stop a view with its name or its view ID. To stop it with its name:
-Countly.instance().views().stopViewWithName("View Name");
+Countly.instance().views().stopViewWithName("View Name");
You can provide a segmentation while doing so:
-Map<String, Object> viewSegmentation = new HashMap<>();
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
Countly.instance().views().stopViewWithName("View Name", viewSegmentation);
- If there are multiple views with the same name then stopViewWithName would close the one that has started first. These views would have different identifiers even though their names are same.
-
-
- To stop a view with its view ID:
-
-Countly.instance().views().stopViewWithID("View ID");
-
- You can provide a segmentation while doing so:
+ If there are multiple views with the same name then
+ stopViewWithName would close the one that
+ has started first. These views would have different identifiers even though their
+ names are same.
-Map<String, Object> viewSegmentation = new HashMap<>();
+To stop a view with its view ID:
+Countly.instance().views().stopViewWithID("View ID");
+You can provide a segmentation while doing so:
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
@@ -457,42 +469,39 @@ Countly.instance().views().stopViewWithID("View ID", viewSegmentation);
You can also stop all running views at once with a segmentation:
-Map<String, Object> viewSegmentation = new HashMap<>();
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
Countly.instance().views().stopAllViews(viewSegmentation); // pass null if no segmentation
-Pausing and Resuming Views
-
- If you are starting multiple views at the same time it might be necessary for you to pause some views while others are still continuing. This can be achieved by using the unique identifier you get while starting a view.
-
-
- To pause a view with its ID:
-
-Countly.instance().views().pauseViewWithID("View ID");
-
- To resume a view with its ID:
-
-Countly.instance().views().resumeViewWithID("View ID");
-Adding Segmentation to Started Views
+Pausing and Resuming Views
- You can add segmentation values to a view before it ends. This can be done as many times as desired and the final segmentation that will be send to the server would be the cumulative sum of all segmentations. However if a certain segmentation value for a specific key has been updated, the latest value will be used.
+ If you are starting multiple views at the same time it might be necessary for
+ you to pause some views while others are still continuing. This can be achieved
+ by using the unique identifier you get while starting a view.
+To pause a view with its ID:
+Countly.instance().views().pauseViewWithID("View ID");
+To resume a view with its ID:
+Countly.instance().views().resumeViewWithID("View ID");
+Adding Segmentation to Started Views
- To add segmentation to a view using its view ID:
+ You can add segmentation values to a view before it ends. This can be done as
+ many times as desired and the final segmentation that will be send to the server
+ would be the cumulative sum of all segmentations. However if a certain segmentation
+ value for a specific key has been updated, the latest value will be used.
-String viewID = Countly.instance().views().startView("View Name");
+To add segmentation to a view using its view ID:
+String viewID = Countly.instance().views().startView("View Name");
Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
Countly.instance().views().addSegmentationToViewWithID(viewID, viewSegmentation);
-
- To add segmentation to a view using its name:
-
-String viewName = "View Name";
+To add segmentation to a view using its name:
+String viewName = "View Name";
Countly.instance().views().startView(viewName);
Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
@@ -514,35 +523,38 @@ Countly.instance().views().addSegmentationToViewWithName(viewName, viewSegmentat
the specific device. For that, you may use the following calls. Current device
id types are 'DEVELOPER_SUPPLIED', 'SDK_GENERATED'.
-Countly.instance().deviceId().getID() // will return String
+Countly.instance().deviceId().getID() // will return String
Countly.instance().deviceId().getType() // will return DeviceIdType enum
Changing Device ID
+You can change the device ID of an user with setID method:
+Countly.instance().deviceId().setID("newId");
- You can change the device ID of an user with setID method:
-
-Countly.instance().deviceId().setID("newId");
-
- This method's effect on the server will be different according to the type of the current ID stored in the SDK at the time you call it:
+ This method's effect on the server will be different according to the type of
+ the current ID stored in the SDK at the time you call it:
- -
-
- If current stored ID is DeviceIdType.SDK_GENERATED then in the server all the information recorded for that device ID will be merged to the new ID you provide and old user with the DeviceIdType.SDK_GENERATED ID will be erased.
-
+ -
+ If current stored ID is
DeviceIdType.SDK_GENERATED then in the
+ server all the information recorded for that device ID will be merged to
+ the new ID you provide and old user with the
+ DeviceIdType.SDK_GENERATED ID will be erased.
- -
-
- If the current stored ID is DeviceIdType.DEVELOPER_SUPPLIED then in the server it will also create a new user with this new ID if it does not exist.
-
+ -
+ If the current stored ID is
DeviceIdType.DEVELOPER_SUPPLIED
+ then in the server it will also create a new user with this new ID if it
+ does not exist.
- If you need a more complicated logic or using the SDK version 24.1.0 and below then you will need to use this method mentioned here instead.
+ If you need a more complicated logic or using the SDK version 24.1.0 and
+ below then you will need to use this method mentioned
+ here instead.
- NOTE: The call will reject invalid device ID values. A valid value is not null and is not an empty string.
+ NOTE: The call will reject invalid device ID values. A valid value is not null
+ and is not an empty string.
Device ID Generation
@@ -551,7 +563,7 @@ Countly.instance().deviceId().getType() // will return DeviceIdType enum
<
ID generation. For example, after you init the SDK without providing a custom
id, this call will return something like this:
-Countly.instance().deviceId().getID(); // CLY_1930183b-77b7-48ce-882a-87a14056c73e
+Countly.instance().deviceId().getID(); // CLY_1930183b-77b7-48ce-882a-87a14056c73e
User Location
You can track your users' location with Countly Java SDK. This information can
@@ -560,26 +572,23 @@ Countly.instance().deviceId().getType() // will return DeviceIdType enum
<
these four parameters regarding a user's location:
- -
- Country code in the two-letter, ISO standard, e.g. "en-US", "zh-CN"
-
- -
- City name (must be set together with the country code), e.g. "Reykjavik"
+
-
+ Country code in the two-letter, ISO standard, e.g. "en-US", "zh-CN"
- -
- Latitude and longitude values, separated by a comma, e.g. "56.42345,123.45325"
+
-
+ City name (must be set together with the country code), e.g. "Reykjavik"
- -
- Your user’s IP address, e.g. "192.168.1.1"
+
-
+ Latitude and longitude values, separated by a comma, e.g. "56.42345,123.45325"
+ - Your user’s IP address, e.g. "192.168.1.1"
-
- Setting Location
-
+Setting Location
- If you set the user location during SDK initialization it will be sent to the server during the start of the user session:
+ If you set the user location during SDK initialization it will be sent to the
+ server during the start of the user session:
-config.setLocation(countryCode, city, gpsCoordinates, ipAddress);
+config.setLocation(countryCode, city, gpsCoordinates, ipAddress);
As server side location calculations depends on the location info coming at the
beginning of a user session, providing this info at init time is recommended.
@@ -588,12 +597,13 @@ Countly.instance().deviceId().getType() // will return DeviceIdType enum
<
If you get your users' location info after SDK initialization, you can still
provide them with the following call:
-//set user location
+//set user location
String countryCode = "us";
String city = "Houston";
String latitude = "29.634933";
String longitude = "-95.220255";
-String ipAddress = null; // IP address must only be provided during init.
+String ipAddress = null; // IP address must only be provided during init.
+
Countly.instance().location().setLocation(countryCode, city, latitude + "," + longitude, ipAddress);
@@ -609,13 +619,13 @@ Countly.instance().location().setLocation(countryCode, city, latitude + "," + lo
To turn off location tracking during init, use this method. Otherwise the location
tracking is enabled by default:
-config.disableLocation();
+config.disableLocation();
To turn off location tracking after init you can use this method:
-Countly.instance().location().disableLocation();
+Countly.instance().location().disableLocation();
- This action will erase the cached location data from the device and the server.
+ This action will erase the cached location data from the device and the server.
Remote Config
@@ -624,58 +634,57 @@ Countly.instance().location().setLocation(countryCode, city, latitude + "," + lo
more details, please see the Remote Config
documentation.
It is accessible through
- Countly.instance().remoteConfig() interface. Remote
- config values are stored when downloaded unless they are deleted. Also, if values
- downloaded with full update, stored values are overwritten by newly downloaded
- values.;
+ Countly.instance().remoteConfig() interface.
+ Remote config values are stored when downloaded unless they are deleted. Also,
+ if values downloaded with full update, stored values are overwritten by newly
+ downloaded values.;
Downloading values
-
- Automatic Remote Config Triggers
-
+Automatic Remote Config Triggers
- Automatic remote config triggers are disabled by default so there is no need for disable action. If you enable it by enableRemoteConfigAutomaticTriggers, the remote config values are going to be fully updated.
+ Automatic remote config triggers are disabled by default so there is no need
+ for disable action. If you enable it by
+ enableRemoteConfigAutomaticTriggers, the remote
+ config values are going to be fully updated.
-Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
+Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.enableRemoteConfigAutomaticTriggers();
...
Remote configs are going to be downloaded from scratch in these triggers:
- -
- Just after initialization of the Countly Java SDK
-
- -
- After a device id change
-
+ - Just after initialization of the Countly Java SDK
+ - After a device id change
Manual Calls
There are three ways to trigger remote config value download manually:
- -
- Manually downloading all keys
-
- -
- Manually downloading specific keys
-
- - Manually downloading, omitting (everything except) keys.
+ - Manually downloading all keys
+ - Manually downloading specific keys
+ - Manually downloading, omitting (everything except) keys.
- Each of these calls also has an optional RCDownloadCallback callback parameter which would be called when the download has finished.
+ Each of these calls also has an optional RCDownloadCallback callback parameter
+ which would be called when the download has finished.
- dowloadAllKeys is the same as the automatic update
- - it replaces all stored values with the ones from the server (all locally stored
- values are deleted and replaced with new ones).
+ dowloadAllKeys is the same as the automatic
+ update - it replaces all stored values with the ones from the server (all locally
+ stored values are deleted and replaced with new ones).
- Or downloading values of only specific keys might be needed. To do so, calling downloadSpecificKeys to download new values for the specific keys would update only those keys which are provided with a String array.
+ Or downloading values of only specific keys might be needed. To do so, calling
+ downloadSpecificKeys to download new values
+ for the specific keys would update only those keys which are provided with a
+ String array.
- Or downloading values of only a few keys might not be needed. To do so, calling downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array.
+ Or downloading values of only a few keys might not be needed. To do so, calling
+ downloadOmittingKeys would update all values
+ except the provided keys. The keys are provided with a String array.
- Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
+ Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
if(requestResult.equals(RequestResult.Success){
//do sth
}
});
- Countly.instance().remoteConfig().downloadSpecificKeys(String[] keysToInclude, (requestResult, error, fullValueUpdate, downloadedValues) -> {
+ Countly.instance().remoteConfig().downloadSpecificKeys(String[] keysToInclude, (requestResult, error, fullValueUpdate, downloadedValues) -> {
if(requestResult.equals(RequestResult.Success){
//do sth
}
});
- Countly.instance().remoteConfig().downloadOmittingKeys(String[] keysToOmit, (requestResult, error, fullValueUpdate, downloadedValues) -> {
+ Countly.instance().remoteConfig().downloadOmittingKeys(String[] keysToOmit, (requestResult, error, fullValueUpdate, downloadedValues) -> {
if(requestResult.equals(RequestResult.Success){
//do sth
}
@@ -706,22 +715,21 @@ config.enableRemoteConfigAutomaticTriggers();
- When making requests with an "keysToInclude" or "keysToOmit" array, if those arrays are empty or null, they will function the same as a dowloadAllKeys request and will update all the values. This means it will also erase all keys not returned by the server.
+ When making requests with an "keysToInclude" or "keysToOmit" array, if those
+ arrays are empty or null, they will function the same as a
+ dowloadAllKeys request and will update all
+ the values. This means it will also erase all keys not returned by the server.
-
- Accessing Values
-
+Accessing Values
There is two way to access remote config values. Either all values can be gathered,
or a value can be obtained for a specific key.
-//Which will return map of all stored remote config values
+//Which will return map of all stored remote config values
Map<String,RCData> allValues = Countly.instance().remoteConfig().getValues();
Object valueOfKey = allValues.get("key").value;
-
- RCData looks like this:
-
-class RCData {
+RCData looks like this:
+class RCData {
//value that is downloaded from the server for that key
Object value;
//metadata about ownership of the value
@@ -729,12 +737,13 @@ Object valueOfKey = allValues.get("key").value;
boolean isCurrentUsersData;
}
- Why value is in Object class? Because all data types supported by JSON can be a stored. For example it can be a JSONArray, JSONObject, Integer, Boolean, Float, String, Double, Long. So it is needed to cast to appropriate data type. If value is null then there is no value for that key found.
-
-
- To get a value's of a specific key:
+ Why value is in Object class? Because all data types supported by JSON can be
+ a stored. For example it can be a JSONArray, JSONObject, Integer, Boolean, Float,
+ String, Double, Long. So it is needed to cast to appropriate data type. If value
+ is null then there is no value for that key found.
-RCData data = Countly.instance().remoteConfig().getValue("key");
+To get a value's of a specific key:
+RCData data = Countly.instance().remoteConfig().getValue("key");
RCData data1 = Countly.instance().remoteConfig().getValue("key1");
RCData data2 = Countly.instance().remoteConfig().getValue("key2");
JSONObject json = (JSONObject) data.value;
@@ -746,14 +755,14 @@ Integer intValue = (Integer) data2.value;
Clearing the remote config values might be needed at some case, so by this call
you can clean the remote config values:
-Countly.instance().remoteConfig().clearAll();
+Countly.instance().remoteConfig().clearAll();
Global Download Callbacks
A callback function might be needed after remote config values downloaded. Remote
config download callback functions can be registered during initialization of
Countly or via remoteConfig interface.
-//during initialization
+//during initialization
Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.remoteConfigRegisterGlobalCallback(RCDownloadCallback callback);
...
@@ -762,32 +771,33 @@ config.remoteConfigRegisterGlobalCallback(RCDownloadCallback callback);
and it would have the following parameters:
- -
-
rResult: RequestResult Enum (either Error, Success
- or NetworkIssue)
+ -
+
rResult: RequestResult Enum (either Error,
+ Success or NetworkIssue)
- -
-
error: String (error message. "null" if there is
- no error)
+ -
+
error: String (error message. "null" if
+ there is no error)
- -
-
fullValueUpdate: boolean ("true" - all values updated,
- "false" - a subset of values updated)
+ -
+
fullValueUpdate: boolean ("true" - all
+ values updated, "false" - a subset of values updated)
- -
-
downloadedValues: Map<String, RCData> (the
- whole downloaded remote config values from server)
+ -
+
downloadedValues: Map<String, RCData>
+ (the whole downloaded remote config values from server)
-RCDownloadCallback {
+RCDownloadCallback {
void callback(RequestResult rResult, String error, boolean fullValueUpdate, Map<String, RCData> downloadedValues);
}
Via remoteConfig interface, already registered callback can be also removed
-//register a download callback
+//register a download callback
Countly.instance().remoteConfig().registerDownloadCallback(RCDownloadCallback callback);
-
//remove already registered download callback
+
+//remove already registered download callback
Countly.instance().remoteConfig().removeDownloadCallback(RCDownloadCallback callback);
A/B Testing
@@ -800,7 +810,7 @@ Countly.instance().remoteConfig().removeDownloadCallback(RCDownloadCallback call
Enrollment to A/B tests can be done automatically when downloading remote config
values. This can be enabled on the initialization of the Countly.
-//during initialization
+//during initialization
Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.enrollABOnRCDownload();
...
@@ -809,7 +819,7 @@ config.enrollABOnRCDownload();
A/B tests can be enrolled while getting remote config values from storage. If
there is no key exists functions does not enroll user to A/B tests:
-//This call returns RCData, works same way with non-enrolling variant getValue
+//This call returns RCData, works same way with non-enrolling variant getValue
Countly.instance().remoteConfig().getValueAndEnroll(String key);
//This call returns Map<String,RCData>, works same way with non-enrolling variant getValues
@@ -819,7 +829,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
To enroll a user into the A/B tests for the given keys following method should
be used:
-Countly.instance().remoteConfig().enrollIntoABTestsForKeys(String[] keys);
+Countly.instance().remoteConfig().enrollIntoABTestsForKeys(String[] keys);
Keys array is the required parameter. If it is not given this function does nothing.
@@ -828,7 +838,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
To remove users from A/B tests of certain keys, following function should be
used:
-Countly.instance().remoteConfig().exitABTestsForKeys(String[] keys);
+Countly.instance().remoteConfig().exitABTestsForKeys(String[] keys);
Keys array is the required parameter. If it is not given, user is going to be
removed from all tests.
@@ -850,10 +860,10 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
It is possible to display 3 kinds of feedback widgets:
- NPS,
- Survey
+ NPS,
+ Survey
and
- Rating.
+ Rating.
All widgets have their generated URL to be shown in a webview and should be approached
using the same methods.
@@ -875,13 +885,13 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
as the first parameter and error as the second:
- Countly.instance().feedback().getAvailableFeedbackWidgets((retrievedWidgets, error) -> {
+ Countly.instance().feedback().getAvailableFeedbackWidgets((retrievedWidgets, error) -> {
// handle error
// do something with the returned list here like pick a widget and then show that widget etc...
});
The objects in the returned list would look like this:
-class CountlyFeedbackWidget {
+class CountlyFeedbackWidget {
public String widgetId;
public FeedbackWidgetType type;
public String name;
@@ -895,7 +905,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
left to the developer.
Potential 'type' values are:
-FeedbackWidgetType {survey, nps, rating}
+FeedbackWidgetType {survey, nps, rating}
Presenting A Widget
After you have decided which widget you want to show, you would provide that
@@ -903,7 +913,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
with constructed url to show and error message in case an error occurred:
- Countly.instance().feedback().constructFeedbackWidgetUrl(chosenWidget, (constructedUrl, error) -> {
+ Countly.instance().feedback().constructFeedbackWidgetUrl(chosenWidget, (constructedUrl, error) -> {
// handle error and the constructed url
});
@@ -919,7 +929,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
For a sample integration, have a look at our
- sample code
+ sample code
at our github repo.
@@ -934,7 +944,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
widget data as first parameter and the error as second:
- Countly.instance().feedback().getFeedbackWidgetData(chosenWidget, (retrievedWidgetData, error) -> {
+ Countly.instance().feedback().getFeedbackWidgetData(chosenWidget, (retrievedWidgetData, error) -> {
// handle data and error here
});
@@ -958,7 +968,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
and the retrievedWidgetData to report the feedback result with the
following call:
-//this contains the reported results
+//this contains the reported results
Map<String, Object> reportedResult = new HashMap<>();
//
@@ -986,10 +996,11 @@ Countly.instance().feedback().reportFeedbackWidgetManually(widgetToReport, retri
Setting Custom Values
To set custom properties, call setProperty(). To send modification operations,
- call the corresponding methods and
- ensure to call Countly.instance().userProfile().save() to send the configured user properties to the server after setting them:
+ call the corresponding methods and ensure to call
+ Countly.instance().userProfile().save() to send the configured user
+ properties to the server after setting them:
-Countly.instance().userProfile().setProperty("mostFavoritePet", "dog");
+Countly.instance().userProfile().setProperty("mostFavoritePet", "dog");
Countly.instance().userProfile().increment("phoneCalls"); // increments by 1
Countly.instance().userProfile().pushUnique("tags", "fan");
Countly.instance().userProfile().pushUnique("skill", "singer");
@@ -1002,29 +1013,29 @@ Countly.instance().userProfile().save();
user:
- -
+
-
Name: Full name of the user.
- -
+
-
Username: Username of the user.
- -
+
-
Email: Email address of the user.
- -
+
-
Organization: Organization the user is working in.
- -
+
-
Phone: Phone number.
- -
+
-
Picture: Picture path for the user’s profile.
- -
+
-
Gender: Gender of the user (use only single char like ‘M’
for Male and ‘F’ for Female).
- -
+
-
BirthYear: Birth year of the user.
@@ -1032,10 +1043,11 @@ Countly.instance().userProfile().save();
The SDK allows you to upload user details using the methods listed below.
- To set standard properties, call respective methods and
- ensure to call Countly.instance().userProfile().save() to send the configured user properties to the server after setting them:
+ To set standard properties, call respective methods and ensure to call
+ Countly.instance().userProfile().save() to send the configured user
+ properties to the server after setting them:
-Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.NAME, "Firstname Lastname");
+Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.NAME, "Firstname Lastname");
Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.EMAIL, "test@test.com");
Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.USERNAME, "nickname");
Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.ORGANIZATION, "Tester");
@@ -1044,15 +1056,15 @@ Countly.instance().userProfile().save();
Setting User Picture
You can either upload a profile picture by this call:
-Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE, BYTE_IMAGE)
+Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE, BYTE_IMAGE)
or you can provide a picture url or local file path to set (only JPG, JPEG files
are supported by the Java SDK):
-Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE_PATH, String)
+Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE_PATH, String)
User Property Modificators
Here is the list of property modificators:
-//set a custom property
+//set a custom property
Countly.instance().userProfile().setProperty("money", 1000);
//increment money by 50
Countly.instance().userProfile().increment("money", 50);
@@ -1081,25 +1093,34 @@ Countly.instance().userProfile().save();
server. If the salt on the Countly server is selected, all requests will be checked
for the validity of the checksum field before being processed.
-Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.enableParameterTamperingProtection("salt");
Countly.instance().init(config);
SSL Certificate Pinning
- Public key and certificate pinning are techniques that improve communication security by eliminating the threat of man-in-the-middle attack (MiM) in SSL connections
+ Public key and certificate pinning are techniques that improve communication
+ security by eliminating the threat of
+ man-in-the-middle attack (MiM)
+ in SSL connections
- When you supply a list of acceptable SSL certificates to Countly SDK with eitherconfig.addPublicKeyPin(String) or config.addCertificatePin(String), it will ensure that connection is made with one of the public keys specified or one of the certificates specified, respectively. Using whole certificate pinning is somewhat safer, but using public key pinning is preferred since certificates can be rotated and do expire while public keys don't (assuming you don't change your CA).
+ When you supply a list of acceptable SSL certificates to Countly SDK with eitherconfig.addPublicKeyPin(String)
+ or config.addCertificatePin(String), it will ensure that connection
+ is made with one of the public keys specified or one of the certificates specified,
+ respectively. Using whole certificate pinning is somewhat safer, but using public
+ key pinning is preferred since certificates can be rotated and do expire while
+ public keys don't (assuming you don't change your CA).
Pinning is done during init through the Config object.
The provided public key or certificate should be in PEM-encoded format.
- For more information on how to acquire the public key or the certificate, have a look here.
+ For more information on how to acquire the public key or the certificate, have
+ a look
+ here.
Here is an example of public key pinning for an example server.
-Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory)
+Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory)
.addPublicKeyPin(
"oIssIjsNsgkqhkiG9s0BAQEFAAsCAs8AMIIBCsgKCAQEtmTk89AsQboS+sMbVoOJ\n"
+ "cXsOz0TWJqNs9M00atZasr//WcNTQvmGps66hWqhYglPFH76CWlsq34aOsHXoHyS\n"
@@ -1131,113 +1152,118 @@ Countly.instance().init(config);
These are the methods that lets you set values in your Countly config object:
- -
+
-
setUpdateSessionTimerDelay(int delay) - Sets the interval
for the automatic session update calls. The delay can not be smaller than
1 sec.
- -
+
-
setEventQueueSizeToSend() - Sets the threshold for event
grouping.
- -
+
-
setSdkPlatform(String platform) - Sets the SDK platform
if it is used in multiple platforms. Default value is OS name.
- -
+
-
setRequiresConsent(boolean requiresConsent) - Enable GDPR
compliance by disallowing SDK to record any data until corresponding consent
calls are made. Default is false.
- -
+
-
setMetricOverride(Map<String, String> metricOverride)
- Mechanism for overriding metrics that are sent together with "begin
session" requests and remote.
- -
+
-
setApplicationVersion(String version) - Change application
version reported to Countly server.
- -
+
-
setApplicationName(String name) - Change application name
reported to Countly server.
- -
+
-
setLoggingLevel(LoggingLevel loggingLevel) - Logging
level for Countly SDK. Default is OFF.
- -
+
-
enableParameterTamperingProtection(String salt) - Enable
parameter tampering protection.
- -
+
-
setRequestQueueMaxSize(int requestQueueMaxSize) - In
backend mode set the in memory request queue size. Default is 1000.
- -
+
-
enableForcedHTTPPost() - Force usage of POST method
for all requests.
- -
+
-
setCustomDeviceId(String customDeviceId) - Set device
id to specific string and strategy to
DeviceIdStrategy.CUSTOM_ID.
- -
+
-
setLogListener(LogCallback logCallback) - Add a log
callback that will duplicate all logs done by the SDK.
- -
+
-
enrollABOnRCDownload() - Enables A/B tests enrollment when
remote config keys downloaded
- -
+
-
remoteConfigRegisterGlobalCallback(RCDownloadCallback callback)
- Register a callback to be called when remote config values is downloaded
- -
+
-
enableRemoteConfigValueCaching() - Enable caching of remote
config values
- -
+
-
enableRemoteConfigAutomaticTriggers() - Enable automatic
download of remote config values on triggers
- -
+
-
disableLocation() - Disable location tracking
- -
+
-
setLocation(String countryCode, String city, String geoLocation, String ipAddress)
- Set location parameters to be sent with session begin
- -
+
-
setMaxBreadcrumbCount(int maxBreadcrumbCount) - To change
maximum limit of crash breadcrumb
- -
+
-
disableUnhandledCrashReporting() - To disable unhandled
crash reporting
- -
+
-
addCustomNetworkRequestHeaders(Map<String, String> customHeaderValues)
- Adds custom header key/value pairs to each request.
+ -
+ disableAutoSendUserProperties() - Disable automatic sending
+ of user properties on triggers: when an event recorded, during an internal
+ tick, when a session call made.
+
Example Integrations
@@ -1261,7 +1287,7 @@ Countly.instance().init(config);
event count reaches a threshold. By default it is 10. If you would like to change
this, call:
-config.setEventQueueSizeToSend(6);
+config.setEventQueueSizeToSend(6);
Setting Maximum Request Queue Size
The request queue is flushed when the session timer delay exceeds. If network
@@ -1270,15 +1296,17 @@ Countly.instance().init(config);
and the newest requests will take their place. by default request queue size
is 1000. You can change this by:
-config.setRequestQueueMaxSize(56);
+config.setRequestQueueMaxSize(56);
Forcing HTTP POST
You can force HTTP POST request for all requests by:
-config.enableForcedHTTPPost();
+config.enableForcedHTTPPost();
Custom HTTP Header Values
- If you want to include custom header key/value pairs in each network request sent to the Countly server, you can use the addCustomNetworkRequestHeaders method during configuration:
+ If you want to include custom header key/value pairs in each network request
+ sent to the Countly server, you can use the addCustomNetworkRequestHeaders method
+ during configuration:
-HashMap<String, String> customHeaderValues = new HashMap<>();
+HashMap<String, String> customHeaderValues = new HashMap<>();
customHeaderValues.put("foo", "bar");
config.addCustomNetworkRequestHeaders(customHeaderValues);
@@ -1298,7 +1326,7 @@ config.addCustomNetworkRequestHeaders(customHeaderValues);
to handle those custom values, they will be ignored.
- Map<String, String> metricOverride = new HashMap<>();
+ Map<String, String> metricOverride = new HashMap<>();
metricOverride.put("SomeKey", "123");
metricOverride.put("_locale", "xx_yy");
@@ -1327,10 +1355,10 @@ Countly.instance().init(config);
Enabling Backend Mode
To enable Backend Mode you should create a config class and call
- enableBackendModeon this object, and later you should
- pass it to the init method.
+ enableBackendModeon this object, and later
+ you should pass it to the init method.
-Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
+Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
.enableBackendMode()
.setRequestQueueMaxSize(500)
.setLoggingLevel(Config.LoggingLevel.DEBUG);
@@ -1340,8 +1368,8 @@ Countly.instance().init(config);
If the Backend Mode is enabled the SDK stores up to a maximum of 1000 requests
by default. Then when this limit is exceeded the SDK will drop the oldest request
from the queue in the memory. To change this request queue limit, call
- setRequestQueueMaxSize on the
- Configobject before the SDK init.
+ setRequestQueueMaxSize on the
+ Configobject before the SDK init.
Recording Data
@@ -1364,27 +1392,27 @@ Countly.instance().init(config);
There are a couple of values that can be set when recording an event.
- -
- deviceID- Device id is mandatory, it can not be empty or null.
+
-
+ deviceID- Device id is mandatory, it can not be empty or null.
- -
- key- This is the main property which would be the identifier/name for that event. It is mandatory and it can not be empty or null.
+
-
+ key- This is the main property which would be the identifier/name for that event. It is mandatory and it can not be empty or null.
- -
- count - A whole positive numerical number value that marks how many times this event has happened. It is optional and if it is provided and its value is less than 1, SDK will automatically set it to 1.
+
-
+ count - A whole positive numerical number value that marks how many times this event has happened. It is optional and if it is provided and its value is less than 1, SDK will automatically set it to 1.
- -
- sum - This value will be summed up across all events in the dashboard. It is optional you may set it null.
+
-
+ sum - This value will be summed up across all events in the dashboard. It is optional you may set it null.
- -
- duration - This value is used for recording and tracking the duration of events. Set it to 0 if you don't want to report any duration.
+
-
+ duration - This value is used for recording and tracking the duration of events. Set it to 0 if you don't want to report any duration.
- -
- segmentation - A map where you can provide custom data for your events to track additional information. It is not a mandatory field, so you may set it to null or empty. It is a map that consists of key and value pairs. The accepted data types for the values are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ segmentation - A map where you can provide custom data for your events to track additional information. It is not a mandatory field, so you may set it to null or empty. It is a map that consists of key and value pairs. The accepted data types for the values are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
Example:
-Map<String, String> segment = new HashMap<String, String>();
+Map<String, String> segment = new HashMap<String, String>();
segment.put("Time Spent", "60");
segment.put("Retry Attempts", "60");
@@ -1402,21 +1430,21 @@ Countly.instance().backendM().recordEvent("device-id", "Event Key", 1, 10.5, 5,
There are a couple of values that can be set when recording an event.
- -
- deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
+
-
+ deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
- -
- name - It is the name of the view and it must not be empty or null.
+
-
+ name - It is the name of the view and it must not be empty or null.
- -
- segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
- -
- timestamp - It is time in milliseconds. It is not mandatory, and you may set it to null.
+
-
+ timestamp - It is time in milliseconds. It is not mandatory, and you may set it to null.
Example:
-Map<String, String> segmentation = new HashMap<String, String>();
+Map<String, String> segmentation = new HashMap<String, String>();
segmentation.put("visit", "1");
segmentation.put("segment", "Windows");
segmentation.put("start", "1");
@@ -1428,34 +1456,32 @@ Countly.instance().backendM().recordView("device-id", "SampleView", segmentation
not be recorded if any of these two parameters is null or empty.
Recording a Crash
-
- To report exceptions provide the following detail:
-
+To report exceptions provide the following detail:
- -
- deviceID - Device id is mandatory, and if it is null or not provided no data will be recorded.
+
-
+ deviceID - Device id is mandatory, and if it is null or not provided no data will be recorded.
- -
- message -
- This is the main property which would be the identifier/name for that event. It should not be null or empty.
+
-
+ message - This is the main property which would be the identifier/name
+ for that event. It should not be null or empty.
- -
- stacktrace -
- A string that describes the contents of the call stack. It
+
-
+ stacktrace - A string that describes the contents of the
+ call stack. It
is mandatory, and should not be null or empty.
- -
- segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
- -
- crashDetail - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs. To know more about crash parameters, click here.
+
-
+ crashDetail - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs. To know more about crash parameters, click here.
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
-Map<String, String> segmentation = new HashMap<String, String>();
+Map<String, String> segmentation = new HashMap<String, String>();
segmentation.put("login page", "authenticate request");
Map<String, String> crashDetails = new HashMap<String, String>();
@@ -1470,7 +1496,7 @@ Countly.instance().backendM().recordException("device-id", "message", "stacktrac
stack trace to record a crash.
For example:
-Map<String, String> segmentation = new HashMap<String, String>();
+Map<String, String> segmentation = new HashMap<String, String>();
segmentation.put("login page", "authenticate request");
Map<String, String> crashDetails = new HashMap<String, String>();
@@ -1488,27 +1514,25 @@ try {
be recorded if it is null.
Recording Sessions
-
- To start a session please provide the following details:
-
+To start a session please provide the following details:
- -
- deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
+
-
+ deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
- -
- metrics - It is a map that contains device and app information as key-value pairs.
- It can be null or empty and the accepted data type for the pairs is "String".
+
-
+ metrics - It is a map that contains device and app information as key-value pairs. It
+ can be null or empty and the accepted data type for the pairs is "String".
- -
- location - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted keys are "city", "country_code", "ip_address", and "location".
+
-
+ location - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted keys are "city", "country_code", "ip_address", and "location".
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
Example:
-Map<String, String> metrics = new HashMap<String, String>();
+Map<String, String> metrics = new HashMap<String, String>();
metrics.put("_os", "Android");
metrics.put("_os_version", "10");
metrics.put("_app_version", "1.2");
@@ -1527,17 +1551,17 @@ Countly.instance().backendM().sessionBegin("device-id", metrics, location, 16466
here.
- To update or end a session please provide the following details:
+ To update or end a session please provide the following details:
- -
- deviceID - Device id is mandatory, if it is null or empty no action will be taken.
+
-
+ deviceID - Device id is mandatory, if it is null or empty no action will be taken.
- -
+
-
duration - It is the duration of a session, you may pass
0 if you don't want to submit a duration.
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
@@ -1545,11 +1569,11 @@ Countly.instance().backendM().sessionBegin("device-id", metrics, location, 16466
Session update:
-
double duration = 60;
+double duration = 60;
Countly.instance().backendM().sessionUpdate("device-id", duration, null);
Session end:
-double duration = 20;
+double duration = 20;
Countly.instance().backendM().sessionEnd("device-id", duration, 1223456767L);
@@ -1562,21 +1586,21 @@ Countly.instance().backendM().sessionEnd("device-id", duration, 1223456767L);
data as user details and custom properties.
- -
- deviceID - Device id is mandatory, if it is null or empty no data will be recorded.
+
-
+ deviceID - Device id is mandatory, if it is null or empty no data will be recorded.
- -
- userProperties -
- It is a map of key/value pairs and it should not be null or
- empty. The accepted data types as a value are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ userProperties - It is a map of key/value pairs and it should
+ not be null or empty. The accepted data types as a value are "String", "Integer",
+ "Double", and "Boolean". All other types will be ignored.
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
For example:
-Map<String, Object> userDetail = new HashMap<>();
+Map<String, Object> userDetail = new HashMap<>();
userDetail.put("name", "Full Name");
userDetail.put("username", "username1");
userDetail.put("email", "user@gmail.com");
@@ -1593,77 +1617,65 @@ userDetail.put("marks", "{$inc: 1}");
Countly.instance().backendM().recordUserProperties("device-id", userDetail, 0);
- You may also perform certain manipulations to your custom property values, such as incrementing the current value on a server by a certain amount or storing an array of values under the same property.
+ You may also perform certain manipulations to your custom property values, such
+ as incrementing the current value on a server by a certain amount or storing
+ an array of values under the same property.
-
- For example:
-
-Map<String, Object> operation = new HashMap<>();
+For example:
+Map<String, Object> operation = new HashMap<>();
userDetail.put("fav-colors", "{$push: black}");
userDetail.put("marks", "{$inc: 1}");
Countly.instance().backendM().recordUserProperties("device-id", userDetail, 0);
- The keys for predefined modification operations are as follows:
+ The keys for predefined modification operations are as follows:
-
-
-
- Key
- Description
-
-
- $inc
-
- increment used value by 1
-
-
-
- $mul
-
- multiply value by the provided value
-
-
-
- $min
-
- minimum value
-
-
-
- $max
-
- maximal value
-
-
-
- $setOnce
-
- set value if it does not exist
-
-
-
- $pull
-
- remove value from an array
-
-
-
- $push
-
- insert value to an array
-
-
-
- $addToSet
-
- insert value to an array of unique values
-
-
-
-
+
+
+
+
+ Key
+ Description
+
+
+
+
+ $inc
+ increment used value by 1
+
+
+ $mul
+ multiply value by the provided value
+
+
+ $min
+ minimum value
+
+
+ $max
+ maximal value
+
+
+ $setOnce
+ set value if it does not exist
+
+
+ $pull
+ remove value from an array
+
+
+ $push
+ insert value to an array
+
+
+ $addToSet
+ insert value to an array of unique values
+
+
+
+
Recording Direct Requests
The SDK allows you to record direct requests to the server. To record a request
@@ -1671,20 +1683,21 @@ Countly.instance().backendM().recordUserProperties("device-id", userDetail, 0);
Here are the details:
- -
- deviceID - Device id is mandatory, so if it is null or empty no data will be recorded.
+
-
+ deviceID - Device id is mandatory, so if it is null or empty no data will be recorded.
- -
- requestData - It is a map of key/value pairs
- and it should not be null or empty. The accepted data type for the value is "String".
+
-
+ requestData - It is a map of key/value pairs and it
+ should not be null or empty. The accepted data type for the value is
+ "String".
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
For example:
- Map<String, String> requestData = new HashMap<>();
+ Map<String, String> requestData = new HashMap<>();
requestData.put("device_id", "device-id-2");
requestData.put("timestamp", "1646640780130");
requestData.put("key-name", "data");
@@ -1695,13 +1708,13 @@ Countly.instance().backendM().recordDirectRequest("device-id-1", requestData, 16
Values in the 'requestData' map will override the base request's respective values. In the above example, 'timestamp' and 'device_id' will be overridden by their respective values in the base request.
- Note: 'sdk_name', 'sdk_version', and 'checksum256' are protected by default and their values will not be overridden by 'requestData'.
+ Note: 'sdk_name', 'sdk_version', and 'checksum256' are protected by default and their values will not be overridden by 'requestData'.
Getting the Request Queue Size
- In case you would like to get the size of the request queue, you can use:
+ In case you would like to get the size of the request queue, you can use:
- int queueSize = Countly.instance().backendM().getQueueSize();
+ int queueSize = Countly.instance().backendM().getQueueSize();
It will return the number of requests in the memory request queue.
@@ -1715,32 +1728,45 @@ Countly.instance().backendM().recordDirectRequest("device-id-1", requestData, 16
Changing Device ID with server merge
- In case your application authenticates users, you might want to change the ID to the one in your backend after he has logged in. This helps you identify a specific user with a specific ID on a device he logs in, and the same scenario can also be used in cases this user logs in using a different way. In this case, any data stored in your Countly server database associated with the current device ID will be transferred (merged) into the user profile with the device id you specified in the following method call:
+ In case your application authenticates users, you might want to change the
+ ID to the one in your backend after he has logged in. This helps you identify
+ a specific user with a specific ID on a device he logs in, and the same scenario
+ can also be used in cases this user logs in using a different way. In this
+ case, any data stored in your Countly server database associated with the
+ current device ID will be transferred (merged) into the user profile with
+ the device id you specified in the following method call:
- Countly.instance().deviceId().changeWithMerge("New Device Id");
+ Countly.instance().deviceId().changeWithMerge("New Device Id");
Changing Device ID without server merge
- You might want to track information about another separate user that starts using your app (changing apps account), or your app enters a state where you no longer can verify the identity of the current user (user logs out). In that case, you can change the current device ID to a new one without merging their data. You would call:
+ You might want to track information about another separate user that starts
+ using your app (changing apps account), or your app enters a state where
+ you no longer can verify the identity of the current user (user logs out).
+ In that case, you can change the current device ID to a new one without merging
+ their data. You would call:
- Countly.instance().deviceId().changeWithoutMerge("New Device Id");
+ Countly.instance().deviceId().changeWithoutMerge("New Device Id");
- Doing it this way, will not merge the previously acquired data with the new id.
+ Doing it this way, will not merge the previously acquired data with the new
+ id.
- Do note that every time you change your deviceId without a merge, it will be interpreted as a new user. Therefore implementing id management in a bad way could inflate the users count by quite a lot.
+ Do note that every time you change your deviceId without a merge, it will
+ be interpreted as a new user. Therefore implementing id management in a bad
+ way could inflate the users count by quite a lot.
FAQ
-
- Where Does the SDK Store the Data?
-
+ Where Does the SDK Store the Data?
- The Countly Java SDK stores data in a directory/file structure. All SDK-related files are stored inside the directory given with sdkStorageRootDirectory parameter to the Config class during init. The SDK creates files for sessions, users, event queues, requests, crashes, and JSON storage to keep the device ID, migration version, etc.
+ The Countly Java SDK stores data in a directory/file structure. All SDK-related
+ files are stored inside the directory given with
+ sdkStorageRootDirectory parameter to the Config class during
+ init. The SDK creates files for sessions, users, event queues, requests,
+ crashes, and JSON storage to keep the device ID, migration version, etc.
-
- What Information Is Collected by the SDK?
-
+ What Information Is Collected by the SDK?
The data that SDKs gather to carry out their tasks and implement the necessary
functionalities is mentioned in
diff --git a/java/next.md b/java/next.md
index 2b41a9a8..bc33fca4 100644
--- a/java/next.md
+++ b/java/next.md
@@ -26,20 +26,20 @@
To add it, you first have to add the MavenCentral repository. For Gradle you
would do it something like this:
-buildscript {
+buildscript {
repositories {
mavenCentral()
}
-}
+}
The dependency can be added as:
-dependencies {
- implementation "ly.count.sdk:java:24.1.3"
-}
+dependencies {
+ implementation "ly.count.sdk:java:24.1.4"
+}
Or as:
-<dependency>
+<dependency>
<groupId>ly.count.sdk</groupId>
<artifactId>java</artifactId>
- <version>24.1.3</version>
+ <version>24.1.4</version>
<type>pom</type>
</dependency>
SDK Integration
@@ -50,7 +50,7 @@
The shortest way to initiate the SDK is with this code snippet:
-File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
+File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
.setLoggingLevel(Config.LoggingLevel.DEBUG)
@@ -100,10 +100,10 @@ Countly.instance().init(config);
to the console.
- Set setLoggingLevel on the config object to enable
- logging:
+ Set setLoggingLevel on the config object to
+ enable logging:
-File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
+File targetFolder = new File("d:\\__COUNTLY\\java_test\\");
Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
.setLoggingLevel(Config.LoggingLevel.DEBUG);
@@ -113,9 +113,11 @@ Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolde
Log Listener
- To listen to the SDK's internal logs, you can call setLogListener on the Config Object. If set, SDK will forward its internal logs to this listener regardless of SDK's loggingLevel .
+ To listen to the SDK's internal logs, you can call setLogListener
+ on the Config Object. If set, SDK will forward its internal logs
+ to this listener regardless of SDK's loggingLevel .
-config.setLogListener((logMessage, logLevel) -> {
+config.setLogListener((logMessage, logLevel) -> {
//print log
});
Crash Reporting
@@ -126,25 +128,27 @@ Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolde
exceptions by default if the consent for crash reporting is given. You can reach
all crash-related functionality from the returned interface on:
-Countly.instance().crashes()
+Countly.instance().crashes()
Automatic Crash Handling
Automatic crash handling is enabled by default. To disable it call this method
on the config object during initialization:
-config.disableUnhandledCrashReporting();
+config.disableUnhandledCrashReporting();
Handled Exceptions
- You might catch an exception or similar error during your app’s runtime. To report them use the following method:
+ You might catch an exception or similar error during your app’s runtime. To report
+ them use the following method:
-Countly.instance().crashes().recordHandledException(Throwable t);
+Countly.instance().crashes().recordHandledException(Throwable t);
// Or you can also add segment to be recorded with the error
Countly.instance().crashes().recordHandledException(Throwable t, Map<String, Object> segment);
- If you have handled an exception and it turns out to be fatal to your app, you may use this call:
+ If you have handled an exception and it turns out to be fatal to your app, you
+ may use this call:
-Countly.instance().crashes().recordUnhandledException(Throwable t);
+Countly.instance().crashes().recordUnhandledException(Throwable t);
// Or you can also add segment to be recorded with the error
Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String, Object> segment);
@@ -155,12 +159,12 @@ Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String
will be sent together with the crash report.
Following command adds crash breadcrumb:
-Countly.instance().crashes().addCrashBreadcrumb(String record);
+Countly.instance().crashes().addCrashBreadcrumb(String record);
The maximum breadcrumb limit is 100. To change the maximum limit use this method
during initialization:
-config.setMaxBreadcrumbCount(int maxBreadcrumbCount);
+config.setMaxBreadcrumbCount(int maxBreadcrumbCount);
Events
Events
@@ -173,23 +177,23 @@ Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String
An Event object contains the following data types:
- -
+
-
name, or event key. Required. A unique string that identifies
the event.
- -
+
-
count - number of times. Required, 1 by default. Like a number
of goods added to the shopping basket.
- -
+
-
sum - sum of something, amount. Optional. Like a total sum of
the basket.
- -
+
-
dur - duration of the event. Optional. For example how much
time users spent checking out.
- -
+
-
segmentation - some data associated with the event. Optional.
It's a Map<String, Object> which can be filled with arbitrary data
like {"category": "Pants", "size": "M"}. The valid data types for segmentation
@@ -203,7 +207,7 @@ Countly.instance().crashes().recordUnhandledException(Throwable t, Map<String
events interface:
- Map<String, Object> segmentation = new HashMap<String, Object>();
+ Map<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("Time Spent", 60);
segmentation.put("Retry Attempts", 60);
@@ -215,43 +219,45 @@ Countly.instance().events().recordEvent("purchase", segmentation, 2, 19.98, 35);
until one of the following happens:
- -
+
-
Config.sendUpdateEachSeconds seconds passed since begin or last
update request in case of automatic session control.
- -
+
-
Config.eventsBufferSize events have been already recorded and
not sent yet.
- -
+
-
Session.update() have been called by the developer.
- -
+
-
Session.end() have been called by the developer or by Countly
SDK in case of automatic session control.
- We have provided an example of recording a purchase event below. Here is a quick summary of the information with which each usage will provide us:
+ We have provided an example of recording a purchase event below.
+ Here is a quick summary of the information with which each usage will provide
+ us:
- -
+
-
Usage 1: how many times the purchase event occurred.
- -
+
-
Usage 2: how many times the purchase event occurred + the
total amount of those purchases.
- -
+
-
Usage 3: how many times the purchase event occurred + from
which countries and application versions those purchases were made.
- -
+
-
Usage 4: how many times the purchase event occurred + the
total amount, both of which are also available, segmented into countries
and application versions.
- -
+
-
Usage 5: how many times the purchase event occurred + the
total amount, both of which are also available, segmented into countries
and application versions + the total duration of those events.
@@ -260,15 +266,15 @@ Countly.instance().events().recordEvent("purchase", segmentation, 2, 19.98, 35);
1. Event key and count
-Countly.instance().events().recordEvent("purchase", 1);
+Countly.instance().events().recordEvent("purchase", 1);
2. Event key, count, and sum
-Countly.instance().events().recordEvent("purchase", 1, 20.3);
+Countly.instance().events().recordEvent("purchase", 1, 20.3);
3. Event key and count with segmentation(s)
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("country", "Germany");
segmentation.put("app_version", 1.0);
@@ -276,21 +282,25 @@ Countly.instance().events().recordEvent("purchase", segmentation, 1);
4. Event key, count, and sum with segmentation(s)
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("country", "Germany");
segmentation.put("app_version", 1.0);
-Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5);
+Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5);
+
5. Event key, count, sum, and duration with segmentation(s)
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("country", "Germany");
segmentation.put("app_version", 1.0);
-Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5, 5.3);
+Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5, 5.3);
+
- Those are only a few examples of what you can do with events. You may extend those examples and use Country, app_version, game_level, time_of_day, and any other segmentation that will provide you with valuable insights.
+ Those are only a few examples of what you can do with events. You may extend
+ those examples and use Country, app_version, game_level, time_of_day, and any
+ other segmentation that will provide you with valuable insights.
Timed Events
@@ -300,39 +310,39 @@ Countly.instance().events().recordEvent("purchase", segmentation, 1, 34.5, 5.3);
The basic use case for timed events is following:
- -
+
-
User starts playing a level "37" of your game, you call
Countly.instance().events().startEvent("LevelTime") to start
tracking how much time a user spends on this level. Also keep your segmentation
values in a map like
-HashMap<String, Object> segmentation = new HashMap<String, Object>();
+HashMap<String, Object> segmentation = new HashMap<String, Object>();
segmentation.put("level", 37);
- -
+
-
Then, something happens when the user is at that level; for example, the user buys some coins. Along with the regular "Purchase" event, you decide you want to segment the "LevelTime" event with purchase information. While ending the event, you also pass the sum value as the purchase amount to the function call.
- -
+
-
Once the user stops playing, you need to stop this event and call:
Countly.instance().events().endEvent("LevelTime",segmentation, 1, 9.99)
- -
+
-
If you decide to cancel the event you can call:
Countly.instance().events().cancelEvent("LevelTime")
Once this event is sent to the server, you'll see:
- -
+
-
how much time users spend on each level (duration per
level
segmentation);
- -
+
-
which levels are generating the most revenue (sum per
level
segmentation);
- -
+
-
which levels are not generating revenue at all since you don't show ad there
(0 sums in
level segmentation).
@@ -356,18 +366,18 @@ segmentation.put("level", 37);
Session lifecycle methods include:
- -
+
-
session.begin() must be called when you want to send begin session
request to the server. This request contains all device metrics: device,
model, carrier, etc.
- -
+
-
session.update() can be called to send a session duration update
to the server along with any events, user properties, and any other data
types supported by Countly SDK. Called each Config.sendUpdateEachSeconds
seconds automatically. It can also be called more often manually.
- -
+
-
session.end() must be called to mark the end of the session.
All the data recorded since the last session.update() or since
session.begin() in case no updates have been sent yet, is sent
@@ -378,10 +388,10 @@ segmentation.put("level", 37);
You can track the views of your application with the Java SDK. With views feature,
you can also create
- flows
+ flows
to see view transitions. Public interface of the views can be accessed via:
-Countly.instance().views()
+Countly.instance().views()
Manual View Reporting
@@ -392,9 +402,11 @@ segmentation.put("level", 37);
Auto Stopped Views
- An easy way to track views is with using the auto stopped views. These views would stop if another view starts. You can start an auto stopped view with or without segmentation like this:
+ An easy way to track views is with using the auto stopped views. These views
+ would stop if another view starts. You can start an auto stopped view with or
+ without segmentation like this:
-// without segmentation, use view id for further manipulation of views
+// without segmentation, use view id for further manipulation of views
String viewID = Countly.instance().views().startAutoStoppedView("View Name");
// Or with segmentation
@@ -407,17 +419,18 @@ viewSegmentation.put("Moose", "Deer");
String view2ID = Countly.instance().views().startAutoStoppedView("View Name", viewSegmentation);
Regular Views
- As opposed to the "auto stopped views", with regular views you can have multiple of them started at the same time, and then you can control them independently.
+ As opposed to the "auto stopped views", with regular views you can have multiple
+ of them started at the same time, and then you can control them independently.
You can start a view that would not close when another view starts, like this:
-Countly.instance().views().startView("View Name");
+Countly.instance().views().startView("View Name");
While manually tracking views, you may add your custom segmentation to them like
this:
-Map<String, Object> viewSegmentation = new HashMap<>();
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
@@ -426,29 +439,28 @@ Countly.instance().views().startView("View Name", viewSegmentation);
These views would also return a string view ID when they are called.
-Stopping Views
+Stopping Views
You can stop a view with its name or its view ID. To stop it with its name:
-Countly.instance().views().stopViewWithName("View Name");
+Countly.instance().views().stopViewWithName("View Name");
You can provide a segmentation while doing so:
-Map<String, Object> viewSegmentation = new HashMap<>();
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
Countly.instance().views().stopViewWithName("View Name", viewSegmentation);
- If there are multiple views with the same name then stopViewWithName would close the one that has started first. These views would have different identifiers even though their names are same.
-
-
- To stop a view with its view ID:
-
-Countly.instance().views().stopViewWithID("View ID");
-
- You can provide a segmentation while doing so:
+ If there are multiple views with the same name then
+ stopViewWithName would close the one that
+ has started first. These views would have different identifiers even though their
+ names are same.
-Map<String, Object> viewSegmentation = new HashMap<>();
+To stop a view with its view ID:
+Countly.instance().views().stopViewWithID("View ID");
+You can provide a segmentation while doing so:
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
@@ -457,42 +469,39 @@ Countly.instance().views().stopViewWithID("View ID", viewSegmentation);
You can also stop all running views at once with a segmentation:
-Map<String, Object> viewSegmentation = new HashMap<>();
+Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
Countly.instance().views().stopAllViews(viewSegmentation); // pass null if no segmentation
-Pausing and Resuming Views
-
- If you are starting multiple views at the same time it might be necessary for you to pause some views while others are still continuing. This can be achieved by using the unique identifier you get while starting a view.
-
-
- To pause a view with its ID:
-
-Countly.instance().views().pauseViewWithID("View ID");
-
- To resume a view with its ID:
-
-Countly.instance().views().resumeViewWithID("View ID");
-Adding Segmentation to Started Views
+Pausing and Resuming Views
- You can add segmentation values to a view before it ends. This can be done as many times as desired and the final segmentation that will be send to the server would be the cumulative sum of all segmentations. However if a certain segmentation value for a specific key has been updated, the latest value will be used.
+ If you are starting multiple views at the same time it might be necessary for
+ you to pause some views while others are still continuing. This can be achieved
+ by using the unique identifier you get while starting a view.
+To pause a view with its ID:
+Countly.instance().views().pauseViewWithID("View ID");
+To resume a view with its ID:
+Countly.instance().views().resumeViewWithID("View ID");
+Adding Segmentation to Started Views
- To add segmentation to a view using its view ID:
+ You can add segmentation values to a view before it ends. This can be done as
+ many times as desired and the final segmentation that will be send to the server
+ would be the cumulative sum of all segmentations. However if a certain segmentation
+ value for a specific key has been updated, the latest value will be used.
-String viewID = Countly.instance().views().startView("View Name");
+To add segmentation to a view using its view ID:
+String viewID = Countly.instance().views().startView("View Name");
Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
viewSegmentation.put("Moons", 9.98d);
viewSegmentation.put("Moose", "Deer");
Countly.instance().views().addSegmentationToViewWithID(viewID, viewSegmentation);
-
- To add segmentation to a view using its name:
-
-String viewName = "View Name";
+To add segmentation to a view using its name:
+String viewName = "View Name";
Countly.instance().views().startView(viewName);
Map<String, Object> viewSegmentation = new HashMap<>();
viewSegmentation.put("Cats", 123);
@@ -514,35 +523,38 @@ Countly.instance().views().addSegmentationToViewWithName(viewName, viewSegmentat
the specific device. For that, you may use the following calls. Current device
id types are 'DEVELOPER_SUPPLIED', 'SDK_GENERATED'.
-Countly.instance().deviceId().getID() // will return String
+Countly.instance().deviceId().getID() // will return String
Countly.instance().deviceId().getType() // will return DeviceIdType enum
Changing Device ID
+You can change the device ID of an user with setID method:
+Countly.instance().deviceId().setID("newId");
- You can change the device ID of an user with setID method:
-
-Countly.instance().deviceId().setID("newId");
-
- This method's effect on the server will be different according to the type of the current ID stored in the SDK at the time you call it:
+ This method's effect on the server will be different according to the type of
+ the current ID stored in the SDK at the time you call it:
- -
-
- If current stored ID is DeviceIdType.SDK_GENERATED then in the server all the information recorded for that device ID will be merged to the new ID you provide and old user with the DeviceIdType.SDK_GENERATED ID will be erased.
-
+ -
+ If current stored ID is
DeviceIdType.SDK_GENERATED then in the
+ server all the information recorded for that device ID will be merged to
+ the new ID you provide and old user with the
+ DeviceIdType.SDK_GENERATED ID will be erased.
- -
-
- If the current stored ID is DeviceIdType.DEVELOPER_SUPPLIED then in the server it will also create a new user with this new ID if it does not exist.
-
+ -
+ If the current stored ID is
DeviceIdType.DEVELOPER_SUPPLIED
+ then in the server it will also create a new user with this new ID if it
+ does not exist.
- If you need a more complicated logic or using the SDK version 24.1.0 and below then you will need to use this method mentioned here instead.
+ If you need a more complicated logic or using the SDK version 24.1.0 and
+ below then you will need to use this method mentioned
+ here instead.
- NOTE: The call will reject invalid device ID values. A valid value is not null and is not an empty string.
+ NOTE: The call will reject invalid device ID values. A valid value is not null
+ and is not an empty string.
Device ID Generation
@@ -551,7 +563,7 @@ Countly.instance().deviceId().getType() // will return DeviceIdType enum
<
ID generation. For example, after you init the SDK without providing a custom
id, this call will return something like this:
-Countly.instance().deviceId().getID(); // CLY_1930183b-77b7-48ce-882a-87a14056c73e
+Countly.instance().deviceId().getID(); // CLY_1930183b-77b7-48ce-882a-87a14056c73e
User Location
You can track your users' location with Countly Java SDK. This information can
@@ -560,26 +572,23 @@ Countly.instance().deviceId().getType() // will return DeviceIdType enum
<
these four parameters regarding a user's location:
- -
- Country code in the two-letter, ISO standard, e.g. "en-US", "zh-CN"
-
- -
- City name (must be set together with the country code), e.g. "Reykjavik"
+
-
+ Country code in the two-letter, ISO standard, e.g. "en-US", "zh-CN"
- -
- Latitude and longitude values, separated by a comma, e.g. "56.42345,123.45325"
+
-
+ City name (must be set together with the country code), e.g. "Reykjavik"
- -
- Your user’s IP address, e.g. "192.168.1.1"
+
-
+ Latitude and longitude values, separated by a comma, e.g. "56.42345,123.45325"
+ - Your user’s IP address, e.g. "192.168.1.1"
-
- Setting Location
-
+Setting Location
- If you set the user location during SDK initialization it will be sent to the server during the start of the user session:
+ If you set the user location during SDK initialization it will be sent to the
+ server during the start of the user session:
-config.setLocation(countryCode, city, gpsCoordinates, ipAddress);
+config.setLocation(countryCode, city, gpsCoordinates, ipAddress);
As server side location calculations depends on the location info coming at the
beginning of a user session, providing this info at init time is recommended.
@@ -588,12 +597,13 @@ Countly.instance().deviceId().getType() // will return DeviceIdType enum
<
If you get your users' location info after SDK initialization, you can still
provide them with the following call:
-//set user location
+//set user location
String countryCode = "us";
String city = "Houston";
String latitude = "29.634933";
String longitude = "-95.220255";
-String ipAddress = null; // IP address must only be provided during init.
+String ipAddress = null; // IP address must only be provided during init.
+
Countly.instance().location().setLocation(countryCode, city, latitude + "," + longitude, ipAddress);
@@ -609,13 +619,13 @@ Countly.instance().location().setLocation(countryCode, city, latitude + "," + lo
To turn off location tracking during init, use this method. Otherwise the location
tracking is enabled by default:
-config.disableLocation();
+config.disableLocation();
To turn off location tracking after init you can use this method:
-Countly.instance().location().disableLocation();
+Countly.instance().location().disableLocation();
- This action will erase the cached location data from the device and the server.
+ This action will erase the cached location data from the device and the server.
Remote Config
@@ -624,58 +634,57 @@ Countly.instance().location().setLocation(countryCode, city, latitude + "," + lo
more details, please see the Remote Config
documentation.
It is accessible through
- Countly.instance().remoteConfig() interface. Remote
- config values are stored when downloaded unless they are deleted. Also, if values
- downloaded with full update, stored values are overwritten by newly downloaded
- values.;
+ Countly.instance().remoteConfig() interface.
+ Remote config values are stored when downloaded unless they are deleted. Also,
+ if values downloaded with full update, stored values are overwritten by newly
+ downloaded values.;
Downloading values
-
- Automatic Remote Config Triggers
-
+Automatic Remote Config Triggers
- Automatic remote config triggers are disabled by default so there is no need for disable action. If you enable it by enableRemoteConfigAutomaticTriggers, the remote config values are going to be fully updated.
+ Automatic remote config triggers are disabled by default so there is no need
+ for disable action. If you enable it by
+ enableRemoteConfigAutomaticTriggers, the remote
+ config values are going to be fully updated.
-Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
+Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.enableRemoteConfigAutomaticTriggers();
...
Remote configs are going to be downloaded from scratch in these triggers:
- -
- Just after initialization of the Countly Java SDK
-
- -
- After a device id change
-
+ - Just after initialization of the Countly Java SDK
+ - After a device id change
Manual Calls
There are three ways to trigger remote config value download manually:
- -
- Manually downloading all keys
-
- -
- Manually downloading specific keys
-
- - Manually downloading, omitting (everything except) keys.
+ - Manually downloading all keys
+ - Manually downloading specific keys
+ - Manually downloading, omitting (everything except) keys.
- Each of these calls also has an optional RCDownloadCallback callback parameter which would be called when the download has finished.
+ Each of these calls also has an optional RCDownloadCallback callback parameter
+ which would be called when the download has finished.
- dowloadAllKeys is the same as the automatic update
- - it replaces all stored values with the ones from the server (all locally stored
- values are deleted and replaced with new ones).
+ dowloadAllKeys is the same as the automatic
+ update - it replaces all stored values with the ones from the server (all locally
+ stored values are deleted and replaced with new ones).
- Or downloading values of only specific keys might be needed. To do so, calling downloadSpecificKeys to download new values for the specific keys would update only those keys which are provided with a String array.
+ Or downloading values of only specific keys might be needed. To do so, calling
+ downloadSpecificKeys to download new values
+ for the specific keys would update only those keys which are provided with a
+ String array.
- Or downloading values of only a few keys might not be needed. To do so, calling downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array.
+ Or downloading values of only a few keys might not be needed. To do so, calling
+ downloadOmittingKeys would update all values
+ except the provided keys. The keys are provided with a String array.
- Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
+ Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
if(requestResult.equals(RequestResult.Success){
//do sth
}
});
- Countly.instance().remoteConfig().downloadSpecificKeys(String[] keysToInclude, (requestResult, error, fullValueUpdate, downloadedValues) -> {
+ Countly.instance().remoteConfig().downloadSpecificKeys(String[] keysToInclude, (requestResult, error, fullValueUpdate, downloadedValues) -> {
if(requestResult.equals(RequestResult.Success){
//do sth
}
});
- Countly.instance().remoteConfig().downloadOmittingKeys(String[] keysToOmit, (requestResult, error, fullValueUpdate, downloadedValues) -> {
+ Countly.instance().remoteConfig().downloadOmittingKeys(String[] keysToOmit, (requestResult, error, fullValueUpdate, downloadedValues) -> {
if(requestResult.equals(RequestResult.Success){
//do sth
}
@@ -706,22 +715,21 @@ config.enableRemoteConfigAutomaticTriggers();
- When making requests with an "keysToInclude" or "keysToOmit" array, if those arrays are empty or null, they will function the same as a dowloadAllKeys request and will update all the values. This means it will also erase all keys not returned by the server.
+ When making requests with an "keysToInclude" or "keysToOmit" array, if those
+ arrays are empty or null, they will function the same as a
+ dowloadAllKeys request and will update all
+ the values. This means it will also erase all keys not returned by the server.
-
- Accessing Values
-
+Accessing Values
There is two way to access remote config values. Either all values can be gathered,
or a value can be obtained for a specific key.
-//Which will return map of all stored remote config values
+//Which will return map of all stored remote config values
Map<String,RCData> allValues = Countly.instance().remoteConfig().getValues();
Object valueOfKey = allValues.get("key").value;
-
- RCData looks like this:
-
-class RCData {
+RCData looks like this:
+class RCData {
//value that is downloaded from the server for that key
Object value;
//metadata about ownership of the value
@@ -729,12 +737,13 @@ Object valueOfKey = allValues.get("key").value;
boolean isCurrentUsersData;
}
- Why value is in Object class? Because all data types supported by JSON can be a stored. For example it can be a JSONArray, JSONObject, Integer, Boolean, Float, String, Double, Long. So it is needed to cast to appropriate data type. If value is null then there is no value for that key found.
-
-
- To get a value's of a specific key:
+ Why value is in Object class? Because all data types supported by JSON can be
+ a stored. For example it can be a JSONArray, JSONObject, Integer, Boolean, Float,
+ String, Double, Long. So it is needed to cast to appropriate data type. If value
+ is null then there is no value for that key found.
-RCData data = Countly.instance().remoteConfig().getValue("key");
+To get a value's of a specific key:
+RCData data = Countly.instance().remoteConfig().getValue("key");
RCData data1 = Countly.instance().remoteConfig().getValue("key1");
RCData data2 = Countly.instance().remoteConfig().getValue("key2");
JSONObject json = (JSONObject) data.value;
@@ -746,14 +755,14 @@ Integer intValue = (Integer) data2.value;
Clearing the remote config values might be needed at some case, so by this call
you can clean the remote config values:
-Countly.instance().remoteConfig().clearAll();
+Countly.instance().remoteConfig().clearAll();
Global Download Callbacks
A callback function might be needed after remote config values downloaded. Remote
config download callback functions can be registered during initialization of
Countly or via remoteConfig interface.
-//during initialization
+//during initialization
Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.remoteConfigRegisterGlobalCallback(RCDownloadCallback callback);
...
@@ -762,32 +771,33 @@ config.remoteConfigRegisterGlobalCallback(RCDownloadCallback callback);
and it would have the following parameters:
- -
-
rResult: RequestResult Enum (either Error, Success
- or NetworkIssue)
+ -
+
rResult: RequestResult Enum (either Error,
+ Success or NetworkIssue)
- -
-
error: String (error message. "null" if there is
- no error)
+ -
+
error: String (error message. "null" if
+ there is no error)
- -
-
fullValueUpdate: boolean ("true" - all values updated,
- "false" - a subset of values updated)
+ -
+
fullValueUpdate: boolean ("true" - all
+ values updated, "false" - a subset of values updated)
- -
-
downloadedValues: Map<String, RCData> (the
- whole downloaded remote config values from server)
+ -
+
downloadedValues: Map<String, RCData>
+ (the whole downloaded remote config values from server)
-RCDownloadCallback {
+RCDownloadCallback {
void callback(RequestResult rResult, String error, boolean fullValueUpdate, Map<String, RCData> downloadedValues);
}
Via remoteConfig interface, already registered callback can be also removed
-//register a download callback
+//register a download callback
Countly.instance().remoteConfig().registerDownloadCallback(RCDownloadCallback callback);
-
//remove already registered download callback
+
+//remove already registered download callback
Countly.instance().remoteConfig().removeDownloadCallback(RCDownloadCallback callback);
A/B Testing
@@ -800,7 +810,7 @@ Countly.instance().remoteConfig().removeDownloadCallback(RCDownloadCallback call
Enrollment to A/B tests can be done automatically when downloading remote config
values. This can be enabled on the initialization of the Countly.
-//during initialization
+//during initialization
Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.enrollABOnRCDownload();
...
@@ -809,7 +819,7 @@ config.enrollABOnRCDownload();
A/B tests can be enrolled while getting remote config values from storage. If
there is no key exists functions does not enroll user to A/B tests:
-//This call returns RCData, works same way with non-enrolling variant getValue
+//This call returns RCData, works same way with non-enrolling variant getValue
Countly.instance().remoteConfig().getValueAndEnroll(String key);
//This call returns Map<String,RCData>, works same way with non-enrolling variant getValues
@@ -819,7 +829,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
To enroll a user into the A/B tests for the given keys following method should
be used:
-Countly.instance().remoteConfig().enrollIntoABTestsForKeys(String[] keys);
+Countly.instance().remoteConfig().enrollIntoABTestsForKeys(String[] keys);
Keys array is the required parameter. If it is not given this function does nothing.
@@ -828,7 +838,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
To remove users from A/B tests of certain keys, following function should be
used:
-Countly.instance().remoteConfig().exitABTestsForKeys(String[] keys);
+Countly.instance().remoteConfig().exitABTestsForKeys(String[] keys);
Keys array is the required parameter. If it is not given, user is going to be
removed from all tests.
@@ -850,10 +860,10 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
It is possible to display 3 kinds of feedback widgets:
- NPS,
- Survey
+ NPS,
+ Survey
and
- Rating.
+ Rating.
All widgets have their generated URL to be shown in a webview and should be approached
using the same methods.
@@ -875,13 +885,13 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
as the first parameter and error as the second:
- Countly.instance().feedback().getAvailableFeedbackWidgets((retrievedWidgets, error) -> {
+ Countly.instance().feedback().getAvailableFeedbackWidgets((retrievedWidgets, error) -> {
// handle error
// do something with the returned list here like pick a widget and then show that widget etc...
});
The objects in the returned list would look like this:
-class CountlyFeedbackWidget {
+class CountlyFeedbackWidget {
public String widgetId;
public FeedbackWidgetType type;
public String name;
@@ -895,7 +905,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
left to the developer.
Potential 'type' values are:
-FeedbackWidgetType {survey, nps, rating}
+FeedbackWidgetType {survey, nps, rating}
Presenting A Widget
After you have decided which widget you want to show, you would provide that
@@ -903,7 +913,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
with constructed url to show and error message in case an error occurred:
- Countly.instance().feedback().constructFeedbackWidgetUrl(chosenWidget, (constructedUrl, error) -> {
+ Countly.instance().feedback().constructFeedbackWidgetUrl(chosenWidget, (constructedUrl, error) -> {
// handle error and the constructed url
});
@@ -919,7 +929,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
For a sample integration, have a look at our
- sample code
+ sample code
at our github repo.
@@ -934,7 +944,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
widget data as first parameter and the error as second:
- Countly.instance().feedback().getFeedbackWidgetData(chosenWidget, (retrievedWidgetData, error) -> {
+ Countly.instance().feedback().getFeedbackWidgetData(chosenWidget, (retrievedWidgetData, error) -> {
// handle data and error here
});
@@ -958,7 +968,7 @@ Countly.instance().remoteConfig().getAllValuesAndEnroll();
and the retrievedWidgetData to report the feedback result with the
following call:
-//this contains the reported results
+//this contains the reported results
Map<String, Object> reportedResult = new HashMap<>();
//
@@ -986,10 +996,11 @@ Countly.instance().feedback().reportFeedbackWidgetManually(widgetToReport, retri
Setting Custom Values
To set custom properties, call setProperty(). To send modification operations,
- call the corresponding methods and
- ensure to call Countly.instance().userProfile().save() to send the configured user properties to the server after setting them:
+ call the corresponding methods and ensure to call
+ Countly.instance().userProfile().save() to send the configured user
+ properties to the server after setting them:
-Countly.instance().userProfile().setProperty("mostFavoritePet", "dog");
+Countly.instance().userProfile().setProperty("mostFavoritePet", "dog");
Countly.instance().userProfile().increment("phoneCalls"); // increments by 1
Countly.instance().userProfile().pushUnique("tags", "fan");
Countly.instance().userProfile().pushUnique("skill", "singer");
@@ -1002,29 +1013,29 @@ Countly.instance().userProfile().save();
user:
- -
+
-
Name: Full name of the user.
- -
+
-
Username: Username of the user.
- -
+
-
Email: Email address of the user.
- -
+
-
Organization: Organization the user is working in.
- -
+
-
Phone: Phone number.
- -
+
-
Picture: Picture path for the user’s profile.
- -
+
-
Gender: Gender of the user (use only single char like ‘M’
for Male and ‘F’ for Female).
- -
+
-
BirthYear: Birth year of the user.
@@ -1032,10 +1043,11 @@ Countly.instance().userProfile().save();
The SDK allows you to upload user details using the methods listed below.
- To set standard properties, call respective methods and
- ensure to call Countly.instance().userProfile().save() to send the configured user properties to the server after setting them:
+ To set standard properties, call respective methods and ensure to call
+ Countly.instance().userProfile().save() to send the configured user
+ properties to the server after setting them:
-Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.NAME, "Firstname Lastname");
+Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.NAME, "Firstname Lastname");
Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.EMAIL, "test@test.com");
Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.USERNAME, "nickname");
Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.ORGANIZATION, "Tester");
@@ -1044,15 +1056,15 @@ Countly.instance().userProfile().save();
Setting User Picture
You can either upload a profile picture by this call:
-Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE, BYTE_IMAGE)
+Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE, BYTE_IMAGE)
or you can provide a picture url or local file path to set (only JPG, JPEG files
are supported by the Java SDK):
-Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE_PATH, String)
+Countly.instance().userProfile().setProperty(PredefinedUserPropertyKeys.PICTURE_PATH, String)
User Property Modificators
Here is the list of property modificators:
-//set a custom property
+//set a custom property
Countly.instance().userProfile().setProperty("money", 1000);
//increment money by 50
Countly.instance().userProfile().increment("money", 50);
@@ -1081,25 +1093,34 @@ Countly.instance().userProfile().save();
server. If the salt on the Countly server is selected, all requests will be checked
for the validity of the checksum field before being processed.
-Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory);
config.enableParameterTamperingProtection("salt");
Countly.instance().init(config);
SSL Certificate Pinning
- Public key and certificate pinning are techniques that improve communication security by eliminating the threat of man-in-the-middle attack (MiM) in SSL connections
+ Public key and certificate pinning are techniques that improve communication
+ security by eliminating the threat of
+ man-in-the-middle attack (MiM)
+ in SSL connections
- When you supply a list of acceptable SSL certificates to Countly SDK with eitherconfig.addPublicKeyPin(String) or config.addCertificatePin(String), it will ensure that connection is made with one of the public keys specified or one of the certificates specified, respectively. Using whole certificate pinning is somewhat safer, but using public key pinning is preferred since certificates can be rotated and do expire while public keys don't (assuming you don't change your CA).
+ When you supply a list of acceptable SSL certificates to Countly SDK with eitherconfig.addPublicKeyPin(String)
+ or config.addCertificatePin(String), it will ensure that connection
+ is made with one of the public keys specified or one of the certificates specified,
+ respectively. Using whole certificate pinning is somewhat safer, but using public
+ key pinning is preferred since certificates can be rotated and do expire while
+ public keys don't (assuming you don't change your CA).
Pinning is done during init through the Config object.
The provided public key or certificate should be in PEM-encoded format.
- For more information on how to acquire the public key or the certificate, have a look here.
+ For more information on how to acquire the public key or the certificate, have
+ a look
+ here.
Here is an example of public key pinning for an example server.
-Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory)
+Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory)
.addPublicKeyPin(
"oIssIjsNsgkqhkiG9s0BAQEFAAsCAs8AMIIBCsgKCAQEtmTk89AsQboS+sMbVoOJ\n"
+ "cXsOz0TWJqNs9M00atZasr//WcNTQvmGps66hWqhYglPFH76CWlsq34aOsHXoHyS\n"
@@ -1131,113 +1152,118 @@ Countly.instance().init(config);
These are the methods that lets you set values in your Countly config object:
- -
+
-
setUpdateSessionTimerDelay(int delay) - Sets the interval
for the automatic session update calls. The delay can not be smaller than
1 sec.
- -
+
-
setEventQueueSizeToSend() - Sets the threshold for event
grouping.
- -
+
-
setSdkPlatform(String platform) - Sets the SDK platform
if it is used in multiple platforms. Default value is OS name.
- -
+
-
setRequiresConsent(boolean requiresConsent) - Enable GDPR
compliance by disallowing SDK to record any data until corresponding consent
calls are made. Default is false.
- -
+
-
setMetricOverride(Map<String, String> metricOverride)
- Mechanism for overriding metrics that are sent together with "begin
session" requests and remote.
- -
+
-
setApplicationVersion(String version) - Change application
version reported to Countly server.
- -
+
-
setApplicationName(String name) - Change application name
reported to Countly server.
- -
+
-
setLoggingLevel(LoggingLevel loggingLevel) - Logging
level for Countly SDK. Default is OFF.
- -
+
-
enableParameterTamperingProtection(String salt) - Enable
parameter tampering protection.
- -
+
-
setRequestQueueMaxSize(int requestQueueMaxSize) - In
backend mode set the in memory request queue size. Default is 1000.
- -
+
-
enableForcedHTTPPost() - Force usage of POST method
for all requests.
- -
+
-
setCustomDeviceId(String customDeviceId) - Set device
id to specific string and strategy to
DeviceIdStrategy.CUSTOM_ID.
- -
+
-
setLogListener(LogCallback logCallback) - Add a log
callback that will duplicate all logs done by the SDK.
- -
+
-
enrollABOnRCDownload() - Enables A/B tests enrollment when
remote config keys downloaded
- -
+
-
remoteConfigRegisterGlobalCallback(RCDownloadCallback callback)
- Register a callback to be called when remote config values is downloaded
- -
+
-
enableRemoteConfigValueCaching() - Enable caching of remote
config values
- -
+
-
enableRemoteConfigAutomaticTriggers() - Enable automatic
download of remote config values on triggers
- -
+
-
disableLocation() - Disable location tracking
- -
+
-
setLocation(String countryCode, String city, String geoLocation, String ipAddress)
- Set location parameters to be sent with session begin
- -
+
-
setMaxBreadcrumbCount(int maxBreadcrumbCount) - To change
maximum limit of crash breadcrumb
- -
+
-
disableUnhandledCrashReporting() - To disable unhandled
crash reporting
- -
+
-
addCustomNetworkRequestHeaders(Map<String, String> customHeaderValues)
- Adds custom header key/value pairs to each request.
+ -
+ disableAutoSendUserProperties() - Disable automatic sending
+ of user properties on triggers: when an event recorded, during an internal
+ tick, when a session call made.
+
Example Integrations
@@ -1261,7 +1287,7 @@ Countly.instance().init(config);
event count reaches a threshold. By default it is 10. If you would like to change
this, call:
-config.setEventQueueSizeToSend(6);
+config.setEventQueueSizeToSend(6);
Setting Maximum Request Queue Size
The request queue is flushed when the session timer delay exceeds. If network
@@ -1270,15 +1296,17 @@ Countly.instance().init(config);
and the newest requests will take their place. by default request queue size
is 1000. You can change this by:
-config.setRequestQueueMaxSize(56);
+config.setRequestQueueMaxSize(56);
Forcing HTTP POST
You can force HTTP POST request for all requests by:
-config.enableForcedHTTPPost();
+config.enableForcedHTTPPost();
Custom HTTP Header Values
- If you want to include custom header key/value pairs in each network request sent to the Countly server, you can use the addCustomNetworkRequestHeaders method during configuration:
+ If you want to include custom header key/value pairs in each network request
+ sent to the Countly server, you can use the addCustomNetworkRequestHeaders method
+ during configuration:
-HashMap<String, String> customHeaderValues = new HashMap<>();
+HashMap<String, String> customHeaderValues = new HashMap<>();
customHeaderValues.put("foo", "bar");
config.addCustomNetworkRequestHeaders(customHeaderValues);
@@ -1298,7 +1326,7 @@ config.addCustomNetworkRequestHeaders(customHeaderValues);
to handle those custom values, they will be ignored.
- Map<String, String> metricOverride = new HashMap<>();
+ Map<String, String> metricOverride = new HashMap<>();
metricOverride.put("SomeKey", "123");
metricOverride.put("_locale", "xx_yy");
@@ -1327,10 +1355,10 @@ Countly.instance().init(config);
Enabling Backend Mode
To enable Backend Mode you should create a config class and call
- enableBackendModeon this object, and later you should
- pass it to the init method.
+ enableBackendModeon this object, and later
+ you should pass it to the init method.
-Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
+Config config = new Config("http://YOUR.SERVER.COM", "YOUR_APP_KEY", targetFolder)
.enableBackendMode()
.setRequestQueueMaxSize(500)
.setLoggingLevel(Config.LoggingLevel.DEBUG);
@@ -1340,8 +1368,8 @@ Countly.instance().init(config);
If the Backend Mode is enabled the SDK stores up to a maximum of 1000 requests
by default. Then when this limit is exceeded the SDK will drop the oldest request
from the queue in the memory. To change this request queue limit, call
- setRequestQueueMaxSize on the
- Configobject before the SDK init.
+ setRequestQueueMaxSize on the
+ Configobject before the SDK init.
Recording Data
@@ -1364,27 +1392,27 @@ Countly.instance().init(config);
There are a couple of values that can be set when recording an event.
- -
- deviceID- Device id is mandatory, it can not be empty or null.
+
-
+ deviceID- Device id is mandatory, it can not be empty or null.
- -
- key- This is the main property which would be the identifier/name for that event. It is mandatory and it can not be empty or null.
+
-
+ key- This is the main property which would be the identifier/name for that event. It is mandatory and it can not be empty or null.
- -
- count - A whole positive numerical number value that marks how many times this event has happened. It is optional and if it is provided and its value is less than 1, SDK will automatically set it to 1.
+
-
+ count - A whole positive numerical number value that marks how many times this event has happened. It is optional and if it is provided and its value is less than 1, SDK will automatically set it to 1.
- -
- sum - This value will be summed up across all events in the dashboard. It is optional you may set it null.
+
-
+ sum - This value will be summed up across all events in the dashboard. It is optional you may set it null.
- -
- duration - This value is used for recording and tracking the duration of events. Set it to 0 if you don't want to report any duration.
+
-
+ duration - This value is used for recording and tracking the duration of events. Set it to 0 if you don't want to report any duration.
- -
- segmentation - A map where you can provide custom data for your events to track additional information. It is not a mandatory field, so you may set it to null or empty. It is a map that consists of key and value pairs. The accepted data types for the values are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ segmentation - A map where you can provide custom data for your events to track additional information. It is not a mandatory field, so you may set it to null or empty. It is a map that consists of key and value pairs. The accepted data types for the values are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
Example:
-Map<String, String> segment = new HashMap<String, String>();
+Map<String, String> segment = new HashMap<String, String>();
segment.put("Time Spent", "60");
segment.put("Retry Attempts", "60");
@@ -1402,21 +1430,21 @@ Countly.instance().backendM().recordEvent("device-id", "Event Key", 1, 10.5, 5,
There are a couple of values that can be set when recording an event.
- -
- deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
+
-
+ deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
- -
- name - It is the name of the view and it must not be empty or null.
+
-
+ name - It is the name of the view and it must not be empty or null.
- -
- segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
- -
- timestamp - It is time in milliseconds. It is not mandatory, and you may set it to null.
+
-
+ timestamp - It is time in milliseconds. It is not mandatory, and you may set it to null.
Example:
-Map<String, String> segmentation = new HashMap<String, String>();
+Map<String, String> segmentation = new HashMap<String, String>();
segmentation.put("visit", "1");
segmentation.put("segment", "Windows");
segmentation.put("start", "1");
@@ -1428,34 +1456,32 @@ Countly.instance().backendM().recordView("device-id", "SampleView", segmentation
not be recorded if any of these two parameters is null or empty.
Recording a Crash
-
- To report exceptions provide the following detail:
-
+To report exceptions provide the following detail:
- -
- deviceID - Device id is mandatory, and if it is null or not provided no data will be recorded.
+
-
+ deviceID - Device id is mandatory, and if it is null or not provided no data will be recorded.
- -
- message -
- This is the main property which would be the identifier/name for that event. It should not be null or empty.
+
-
+ message - This is the main property which would be the identifier/name
+ for that event. It should not be null or empty.
- -
- stacktrace -
- A string that describes the contents of the call stack. It
+
-
+ stacktrace - A string that describes the contents of the
+ call stack. It
is mandatory, and should not be null or empty.
- -
- segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ segmentation - A map where you can provide custom data for your view to track additional information. It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted data types are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
- -
- crashDetail - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs. To know more about crash parameters, click here.
+
-
+ crashDetail - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs. To know more about crash parameters, click here.
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
-Map<String, String> segmentation = new HashMap<String, String>();
+Map<String, String> segmentation = new HashMap<String, String>();
segmentation.put("login page", "authenticate request");
Map<String, String> crashDetails = new HashMap<String, String>();
@@ -1470,7 +1496,7 @@ Countly.instance().backendM().recordException("device-id", "message", "stacktrac
stack trace to record a crash.
For example:
-Map<String, String> segmentation = new HashMap<String, String>();
+Map<String, String> segmentation = new HashMap<String, String>();
segmentation.put("login page", "authenticate request");
Map<String, String> crashDetails = new HashMap<String, String>();
@@ -1488,27 +1514,25 @@ try {
be recorded if it is null.
Recording Sessions
-
- To start a session please provide the following details:
-
+To start a session please provide the following details:
- -
- deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
+
-
+ deviceID - Device id is mandatory, if it is null or empty data will not be recorded.
- -
- metrics - It is a map that contains device and app information as key-value pairs.
- It can be null or empty and the accepted data type for the pairs is "String".
+
-
+ metrics - It is a map that contains device and app information as key-value pairs. It
+ can be null or empty and the accepted data type for the pairs is "String".
- -
- location - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted keys are "city", "country_code", "ip_address", and "location".
+
-
+ location - It is not a mandatory field, so you may set it to null or leave it empty. It is a map of key/value pairs and the accepted keys are "city", "country_code", "ip_address", and "location".
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
Example:
-Map<String, String> metrics = new HashMap<String, String>();
+Map<String, String> metrics = new HashMap<String, String>();
metrics.put("_os", "Android");
metrics.put("_os_version", "10");
metrics.put("_app_version", "1.2");
@@ -1527,17 +1551,17 @@ Countly.instance().backendM().sessionBegin("device-id", metrics, location, 16466
here.
- To update or end a session please provide the following details:
+ To update or end a session please provide the following details:
- -
- deviceID - Device id is mandatory, if it is null or empty no action will be taken.
+
-
+ deviceID - Device id is mandatory, if it is null or empty no action will be taken.
- -
+
-
duration - It is the duration of a session, you may pass
0 if you don't want to submit a duration.
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
@@ -1545,11 +1569,11 @@ Countly.instance().backendM().sessionBegin("device-id", metrics, location, 16466
Session update:
-
double duration = 60;
+double duration = 60;
Countly.instance().backendM().sessionUpdate("device-id", duration, null);
Session end:
-double duration = 20;
+double duration = 20;
Countly.instance().backendM().sessionEnd("device-id", duration, 1223456767L);
@@ -1562,21 +1586,21 @@ Countly.instance().backendM().sessionEnd("device-id", duration, 1223456767L);
data as user details and custom properties.
- -
- deviceID - Device id is mandatory, if it is null or empty no data will be recorded.
+
-
+ deviceID - Device id is mandatory, if it is null or empty no data will be recorded.
- -
- userProperties -
- It is a map of key/value pairs and it should not be null or
- empty. The accepted data types as a value are "String", "Integer", "Double", and "Boolean". All other types will be ignored.
+
-
+ userProperties - It is a map of key/value pairs and it should
+ not be null or empty. The accepted data types as a value are "String", "Integer",
+ "Double", and "Boolean". All other types will be ignored.
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
For example:
-Map<String, Object> userDetail = new HashMap<>();
+Map<String, Object> userDetail = new HashMap<>();
userDetail.put("name", "Full Name");
userDetail.put("username", "username1");
userDetail.put("email", "user@gmail.com");
@@ -1593,77 +1617,65 @@ userDetail.put("marks", "{$inc: 1}");
Countly.instance().backendM().recordUserProperties("device-id", userDetail, 0);
- You may also perform certain manipulations to your custom property values, such as incrementing the current value on a server by a certain amount or storing an array of values under the same property.
+ You may also perform certain manipulations to your custom property values, such
+ as incrementing the current value on a server by a certain amount or storing
+ an array of values under the same property.
-
- For example:
-
-Map<String, Object> operation = new HashMap<>();
+For example:
+Map<String, Object> operation = new HashMap<>();
userDetail.put("fav-colors", "{$push: black}");
userDetail.put("marks", "{$inc: 1}");
Countly.instance().backendM().recordUserProperties("device-id", userDetail, 0);
- The keys for predefined modification operations are as follows:
+ The keys for predefined modification operations are as follows:
-
-
-
- Key
- Description
-
-
- $inc
-
- increment used value by 1
-
-
-
- $mul
-
- multiply value by the provided value
-
-
-
- $min
-
- minimum value
-
-
-
- $max
-
- maximal value
-
-
-
- $setOnce
-
- set value if it does not exist
-
-
-
- $pull
-
- remove value from an array
-
-
-
- $push
-
- insert value to an array
-
-
-
- $addToSet
-
- insert value to an array of unique values
-
-
-
-
+
+
+
+
+ Key
+ Description
+
+
+
+
+ $inc
+ increment used value by 1
+
+
+ $mul
+ multiply value by the provided value
+
+
+ $min
+ minimum value
+
+
+ $max
+ maximal value
+
+
+ $setOnce
+ set value if it does not exist
+
+
+ $pull
+ remove value from an array
+
+
+ $push
+ insert value to an array
+
+
+ $addToSet
+ insert value to an array of unique values
+
+
+
+
Recording Direct Requests
The SDK allows you to record direct requests to the server. To record a request
@@ -1671,20 +1683,21 @@ Countly.instance().backendM().recordUserProperties("device-id", userDetail, 0);
Here are the details:
- -
- deviceID - Device id is mandatory, so if it is null or empty no data will be recorded.
+
-
+ deviceID - Device id is mandatory, so if it is null or empty no data will be recorded.
- -
- requestData - It is a map of key/value pairs
- and it should not be null or empty. The accepted data type for the value is "String".
+
-
+ requestData - It is a map of key/value pairs and it
+ should not be null or empty. The accepted data type for the value is
+ "String".
- -
+
-
timestamp -
It is time in milliseconds. It is not mandatory, and you may set it to null.
For example:
- Map<String, String> requestData = new HashMap<>();
+ Map<String, String> requestData = new HashMap<>();
requestData.put("device_id", "device-id-2");
requestData.put("timestamp", "1646640780130");
requestData.put("key-name", "data");
@@ -1695,13 +1708,13 @@ Countly.instance().backendM().recordDirectRequest("device-id-1", requestData, 16
Values in the 'requestData' map will override the base request's respective values. In the above example, 'timestamp' and 'device_id' will be overridden by their respective values in the base request.
- Note: 'sdk_name', 'sdk_version', and 'checksum256' are protected by default and their values will not be overridden by 'requestData'.
+ Note: 'sdk_name', 'sdk_version', and 'checksum256' are protected by default and their values will not be overridden by 'requestData'.
Getting the Request Queue Size
- In case you would like to get the size of the request queue, you can use:
+ In case you would like to get the size of the request queue, you can use:
- int queueSize = Countly.instance().backendM().getQueueSize();
+ int queueSize = Countly.instance().backendM().getQueueSize();
It will return the number of requests in the memory request queue.
@@ -1715,32 +1728,45 @@ Countly.instance().backendM().recordDirectRequest("device-id-1", requestData, 16
Changing Device ID with server merge
- In case your application authenticates users, you might want to change the ID to the one in your backend after he has logged in. This helps you identify a specific user with a specific ID on a device he logs in, and the same scenario can also be used in cases this user logs in using a different way. In this case, any data stored in your Countly server database associated with the current device ID will be transferred (merged) into the user profile with the device id you specified in the following method call:
+ In case your application authenticates users, you might want to change the
+ ID to the one in your backend after he has logged in. This helps you identify
+ a specific user with a specific ID on a device he logs in, and the same scenario
+ can also be used in cases this user logs in using a different way. In this
+ case, any data stored in your Countly server database associated with the
+ current device ID will be transferred (merged) into the user profile with
+ the device id you specified in the following method call:
- Countly.instance().deviceId().changeWithMerge("New Device Id");
+ Countly.instance().deviceId().changeWithMerge("New Device Id");
Changing Device ID without server merge
- You might want to track information about another separate user that starts using your app (changing apps account), or your app enters a state where you no longer can verify the identity of the current user (user logs out). In that case, you can change the current device ID to a new one without merging their data. You would call:
+ You might want to track information about another separate user that starts
+ using your app (changing apps account), or your app enters a state where
+ you no longer can verify the identity of the current user (user logs out).
+ In that case, you can change the current device ID to a new one without merging
+ their data. You would call:
- Countly.instance().deviceId().changeWithoutMerge("New Device Id");
+ Countly.instance().deviceId().changeWithoutMerge("New Device Id");
- Doing it this way, will not merge the previously acquired data with the new id.
+ Doing it this way, will not merge the previously acquired data with the new
+ id.
- Do note that every time you change your deviceId without a merge, it will be interpreted as a new user. Therefore implementing id management in a bad way could inflate the users count by quite a lot.
+ Do note that every time you change your deviceId without a merge, it will
+ be interpreted as a new user. Therefore implementing id management in a bad
+ way could inflate the users count by quite a lot.
FAQ
-
- Where Does the SDK Store the Data?
-
+ Where Does the SDK Store the Data?
- The Countly Java SDK stores data in a directory/file structure. All SDK-related files are stored inside the directory given with sdkStorageRootDirectory parameter to the Config class during init. The SDK creates files for sessions, users, event queues, requests, crashes, and JSON storage to keep the device ID, migration version, etc.
+ The Countly Java SDK stores data in a directory/file structure. All SDK-related
+ files are stored inside the directory given with
+ sdkStorageRootDirectory parameter to the Config class during
+ init. The SDK creates files for sessions, users, event queues, requests,
+ crashes, and JSON storage to keep the device ID, migration version, etc.
-
- What Information Is Collected by the SDK?
-
+ What Information Is Collected by the SDK?
The data that SDKs gather to carry out their tasks and implement the necessary
functionalities is mentioned in
From ae6da98c02faac41b8b110037efe1fc53af02823 Mon Sep 17 00:00:00 2001
From: Arif Burak Demiray
Date: Tue, 30 Dec 2025 10:53:53 +0300
Subject: [PATCH 2/8] feat: migrate current doc of windows
---
windows/current.md | 682 ++++++++++++++++++++++++---------------------
1 file changed, 360 insertions(+), 322 deletions(-)
diff --git a/windows/current.md b/windows/current.md
index 3e38faad..b5169264 100644
--- a/windows/current.md
+++ b/windows/current.md
@@ -15,13 +15,9 @@
The Countly Windows SDK implements the following explicit flavors:
- - .NET Standard 2.0
- -
- .NET Framework 3.5
-
- -
- .NET Framework 4.5
-
+ - .NET Standard 2.0
+ - .NET Framework 3.5
+ - .NET Framework 4.5
@@ -30,19 +26,22 @@
Adding the SDK to the Project
- To install the package, you can use either the NuGet Package Manager or the Package Manager Console. When you install a package, NuGet records the dependency, either in your project file or a packages.config file (depending on the project format).
+ To install the package, you can use either the NuGet Package Manager or the Package
+ Manager Console. When you install a package, NuGet records the dependency, either
+ in your project file or a packages.config file (depending on the
+ project format).
- -
+
-
In Solution Explorer, right-click References and choose
Manage NuGet Packages.
- -
- Choose "nuget.org" as the Package source, select the Browse tab, search for Countly, select that package in the list, and select Install:
-
- -
-
Accept any license prompts.
+ -
+ Choose "nuget.org" as the Package source, select the
+ Browse tab, search for Countly, select
+ that package in the list, and select Install:
+ - Accept any license prompts.
SDK Integration
@@ -50,10 +49,8 @@
Countly.Instance.Init to initiate the SDK.
Minimal Setup
-
- The shortest way to initiate the SDK is with this call:
-
-//create the Countly init object
+The shortest way to initiate the SDK is with this call:
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -62,7 +59,11 @@ cc.appVersion = "1.2.3";
//initiate the SDK with your preferences
Countly.Instance.Init(cc);
- In the CountlyConfig object, you provide appKey and your Countly server URL. Please check here for more information on how to acquire your application key (APP_KEY) and server URL.
+ In the CountlyConfig object, you provide appKey and your Countly
+ server URL. Please check
+ here
+ for more information on how to acquire your application key (APP_KEY) and server
+ URL.
Note: The SDK targets multiple profiles. Therefore for some
@@ -92,14 +93,14 @@ Countly.Instance.Init(cc);
is a call where you can change the path for the named storage folder. You can
change that by using this:
-Countly.SetCustomDataPath("C:\path\to\new\folder\");
+Countly.SetCustomDataPath("C:\path\to\new\folder\");
SDK Notes
Additional Info for UWP Project Setup
It's possible to register an unhandled crash handler during SDK initialization.
To do that, you need to provide a link to your application.
-var cc = new CountlyConfig
+var cc = new CountlyConfig
{
serverUrl = "SERVER_URL",
appKey = "APP_KEY",
@@ -109,21 +110,23 @@ Countly.Instance.Init(cc);
await Countly.Instance.Init(cc);
SDK Logging / Debug Mode
- The first thing you should do while integrating our SDK is to enable logging. If logging is enabled, then our SDK will print out debug messages about its internal state and encounter problems.
- To enable logging you need to do the following two steps:
+ The first thing you should do while integrating our SDK is to enable logging.
+ If logging is enabled, then our SDK will print out debug messages about its internal
+ state and encounter problems. To enable logging you need to do the following
+ two steps:
Step 1: Enable SDK logging using the following call:
-Countly.IsLoggingEnabled = true;
+Countly.IsLoggingEnabled = true;
You can turn it on and off in any place of your code.
- Step 2:
- Go to project properties, select the 'Build' tab and make sure the following things are correct.
+ Step 2: Go to project properties, select the 'Build' tab and
+ make sure the following things are correct.
- - Configuration: Debug
- - "Define DEBUG constant" is checked
+ - Configuration: Debug
+ - "Define DEBUG constant" is checked
@@ -136,7 +139,9 @@ await Countly.Instance.Init(cc);
Crash Reporting
- The Countly SDK for Windows can collect Crash Reports, which you may examine and resolve later on the server.
+ The Countly SDK for Windows can collect
+ Crash Reports,
+ which you may examine and resolve later on the server.
Automatic Crash Handling
@@ -147,10 +152,9 @@ await Countly.Instance.Init(cc);
In that case you would subscribe to that handler and report the received crash
to the SDK with the handled exception method
- RecordException given below by providing
- the unhandled parameter as true. This
- way the crash details and device properties would be saved and sent to the server
- on the next app launch.
+ RecordException given below by providing the
+ unhandled parameter as true. This way the crash details
+ and device properties would be saved and sent to the server on the next app launch.
You can check some platform specific recommendations from
@@ -158,9 +162,12 @@ await Countly.Instance.Init(cc);
Handled Exceptions
- You might catch an exception or similar error during your app’s runtime. You may also log these handled exceptions to monitor how and when they are happening. To log exceptions you can use the RecordException method:
+ You might catch an exception or similar error during your app’s runtime. You
+ may also log these handled exceptions to monitor how and when they are happening.
+ To log exceptions you can use the RecordException
+ method:
-Dictionary<string, string> customInfo = new Dictionary<string, string>{
+Dictionary<string, string> customInfo = new Dictionary<string, string>{
{ "customData", "customValue" }
};
@@ -171,97 +178,105 @@ try {
}
Parameters it takes are:
- -
- error - A string that contains a detailed description
- of the exception.
+
-
+ error - A string that contains a detailed description of
+ the exception.
- -
- stackTrace - A string that describes the contents
- of the call stack.
+
-
+ stackTrace - A string that describes the contents of the
+ call stack.
- -
- customInfo - A Dictionary with string key/value
- pairs to be reported with the crash.
+
-
+ customInfo - A Dictionary with string key/value pairs to
+ be reported with the crash.
- -
+
-
unhandled - A bool value indicating if the crash was fatal
or not.
- If unhandled is set to true the crash report will be saved and sent at the next app start.
+ If unhandled is set to true the crash report will be
+ saved and sent at the next app start.
- If you have handled an exception and it turns out to be fatal to your app, you may use the following shorthand method:
+ If you have handled an exception and it turns out to be fatal to your app, you
+ may use the following shorthand method:
-Countly.RecordUnhandledException(ex.Message, ex.StackTrace);
+Countly.RecordUnhandledException(ex.Message, ex.StackTrace);
Crash Breadcrumbs
- Throughout your app, you can record string values (crash breadcrumbs)
- that could describe previous steps that were taken in your app before
- the crash. After a crash happens, they will be sent together with the crash report.
+ Throughout your app, you can record string values (crash breadcrumbs) that could
+ describe previous steps that were taken in your app before the crash. After a
+ crash happens, they will be sent together with the crash report.
The following command adds a crash breadcrumb:
-Countly.Instance.AddCrashBreadCrumb("breadcrumb");
+Countly.Instance.AddCrashBreadCrumb("breadcrumb");
Consent
- This feature uses Crashes consent. No additional crash logs will be recorded if consent is required and not given.
+ This feature uses Crashes consent. No additional crash logs will
+ be recorded if consent is required and not given.
Events
- An event is any type of action that you can send to a Countly instance, e.g. purchases, changed settings, view enabled, and so on, letting you get valuable information about your application.
+ An event is any type
+ of action that you can send to a Countly instance, e.g. purchases, changed settings,
+ view enabled, and so on, letting you get valuable information about your application.
- There are a couple of values that can be set when recording an event. The main one is the key property which would be the identifier/name for that event. For example, in case a user purchased an item in a game, you could create an event with the key 'purchase'.
+ There are a couple of values that can be set when recording an event. The main
+ one is the key property which would be the identifier/name for
+ that event. For example, in case a user purchased an item in a game, you could
+ create an event with the key 'purchase'.
- Optionally there are also other properties that you might want to set:
+ Optionally there are also other properties that you might want to set:
- -
+
-
Count - a whole numerical value that marks how many times
this event has happened. The default value for that is 1.
- -
+
-
Sum - This value would be summed across all events in the
- dashboard. For example, in-app purchase events sum of purchased items. Its default value is null.
+ dashboard. For example, in-app purchase events sum of purchased items. Its
+ default value is null.
- -
+
-
Duration - Used to record and track the duration of events.
- The default value is null.
+ The default value is null.
- -
+
-
Segmentation- A value where you can provide custom segmentation
for your events to track additional information. It is a key and value map.
- The accepted data types for the value are "String".
+ The accepted data types for the value are "String".
Recording Events
+Here is a quick way to record an event:
+Countly.RecordEvent("event-key");
- Here is a quick way to record an event:
-
-Countly.RecordEvent("event-key");
-
- Based on the example below of an event recording a purchase, here is a quick summary of the information for each usage:
+ Based on the example below of an event recording a purchase,
+ here is a quick summary of the information for each usage:
- -
+
-
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.
@@ -270,15 +285,15 @@ try {
1. Event key and count
-await Countly.RecordEvent("purchase", 3);
+await Countly.RecordEvent("purchase", 3);
2. Event key, count, and sum
-await Countly.RecordEvent("purchase", 3, 0.99);
+await Countly.RecordEvent("purchase", 3, 0.99);
3. Event key and count with segmentation(s)
-Segmentation segmentation = new Segmentation();
+Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");
@@ -287,7 +302,7 @@ await Countly.RecordEvent("purchase", 3, segmentation);
4. Event key, count, and sum with segmentation(s)
-Segmentation segmentation = new Segmentation();
+Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");
@@ -296,13 +311,15 @@ await Countly.RecordEvent("purchase", 3, 2.97, segmentation);
5. Event key, count, sum, duration with segmentation(s)
-Segmentation segmentation = new Segmentation();
+Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");
await Countly.RecordEvent("purchase", 3, 2.97, 122.45, segmentation);
- These are only a few examples of what you can do with Events. You may go beyond those examples and use country, app_version, time_of_day, and any other segmentation of your choice that will provide you with valuable insights.
+ These are only a few examples of what you can do with Events. You may go beyond
+ those examples and use country, app_version, time_of_day, and any other segmentation
+ of your choice that will provide you with valuable insights.
Timed Events
@@ -318,7 +335,7 @@ await Countly.RecordEvent("purchase", 3, 2.97, 122.45, segmentation);
-string eventName = "Some event";
+string eventName = "Some event";
//start some event with the event name "Some event"
Countly.Instance.StartEvent(eventName);
@@ -327,9 +344,11 @@ Countly.Instance.StartEvent(eventName);
//end the event with the same event name "Some event"
Countly.Instance.EndEvent(eventName);
- You may also provide additional information when ending an event. In that case, you can provide the segmentation, count, or sum values. The default values for those are "null", 1, and 0.
+ You may also provide additional information when ending an event. In that case,
+ you can provide the segmentation, count, or sum values. The default values for
+ those are "null", 1, and 0.
-string eventName = "Some event";
+string eventName = "Some event";
//start some event
Countly.Instance.StartEvent(eventName);
@@ -342,13 +361,15 @@ segmentation.Add("wall", "orange");
Countly.Instance.EndEvent(eventName, segmentation);
Here are other options to end timed events:
-//end the event while providing segmentation information and count
-Countly.Instance.EndEvent("timed-event", segmentation, 4);
//end the event while providing segmentation information, count and sum
+//end the event while providing segmentation information and count
+Countly.Instance.EndEvent("timed-event", segmentation, 4);
+
+//end the event while providing segmentation information, count and sum
Countly.Instance.EndEvent("timed-event", segmentation, 4, 10);
You may cancel an already started timed event in case it is not needed anymore:
-//start some event
+//start some event
Countly.Instance.StartEvent(eventName);
//wait some time
@@ -356,10 +377,11 @@ Countly.Instance.StartEvent(eventName);
Countly.Instance.CancelEvent(eventName);
Consent
- This feature uses Events consent. No additional events will be recorded if consent is required and not given.
+ This feature uses Events consent. No additional events will be recorded
+ if consent is required and not given.
- When consent is removed, all previously started timed events will be cancelled.
+ When consent is removed, all previously started timed events will be cancelled.
Sessions
Manual Sessions
@@ -383,7 +405,7 @@ Countly.Instance.CancelEvent(eventName);
Countly.Instance.SessionUpdate(elapsedTime) to track the passage
of time. You should still call it about every minute.
-//start the user session
+//start the user session
Countly.Instance.SessionBegin();
//end the user session
@@ -400,7 +422,7 @@ Countly.Instance.SessionUpdate(elapsedTime);
here. You only need
to provide the name for the view.
-Countly.Instance.RecordView("Some View");
+Countly.Instance.RecordView("Some View");
Device ID Management
To link events, sessions, crashes, etc to a user, a deviceId is used. It is usually
@@ -417,12 +439,12 @@ Countly.Instance.SessionUpdate(elapsedTime);
cons and some limited to a specific compilation target:
- -
+
-
windowsGUID - [all platforms] generates a random GUID that
will be used as a device id. Very high chance of being unique. Will generate
a new id on a reinstall.
- -
+
-
developerSupplied - The device Id was provided by the developer.
Used in cases where developers want to use an id tied to their internal systems/servers.
@@ -431,7 +453,7 @@ Countly.Instance.SessionUpdate(elapsedTime);
Device id and generation method can be provided during SDK init. Those values
can also bet not set, then the default method for that target will be used.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -441,40 +463,41 @@ cc.developerProvidedDeviceId = "use@email.com";
//initiate the SDK with your preferences
Countly.Instance.Init(cc);
Changing Device ID
+You can change the device ID of a user with SetId method:
+await Countly.Instance.SetId("new-device-id");
- You can change the device ID of a user with SetId method:
-
-await Countly.Instance.SetId("new-device-id");
-
- This method's effect on the server will be different according to the type of the current ID stored in the SDK at the time you call it:
+ This method's effect on the server will be different according to the type of
+ the current ID stored in the SDK at the time you call it:
- -
-
- If current stored ID is DeviceIdType.SDKGenerated then in the server all the information recorded for that device ID will be merged to the new ID you provide and old user with the DeviceIdType.SDKGenerated ID will be erased.
-
+ -
+ If current stored ID is
DeviceIdType.SDKGenerated then in the
+ server all the information recorded for that device ID will be merged to
+ the new ID you provide and old user with the
+ DeviceIdType.SDKGenerated ID will be erased.
- -
-
- If the current stored ID is DeviceIdType.DeveloperProvided then in the server it will also create a new user with this new ID if it does not exist.
-
+ -
+ If the current stored ID is
DeviceIdType.DeveloperProvided then
+ in the server it will also create a new user with this new ID if it does
+ not exist.
- NOTE: The call will reject invalid device ID values. A valid value is not null and is not an empty string.
+ NOTE: The call will reject invalid device ID values. A valid value is not null
+ and is not an empty string.
Retrieving Current Device ID
You may want to see what device id Countly is assigning for the specific device.
For that, you may use the following calls.
-string usedId = await Countly.GetDeviceId();
+string usedId = await Countly.GetDeviceId();
SDK record the type of an id. These types are:
- -
+
-
DeveloperProvided
- -
+
-
SDKGenerated
@@ -485,11 +508,11 @@ Countly.Instance.Init(cc);
You can get the device ID type of a user by calling the GetDeviceIDType function:
-var idType = Countly.Instance.GetDeviceIDType();
+var idType = Countly.Instance.GetDeviceIDType();
You can use the DeviceIdType enums to evaluate the device ID type you retrieved:
-var idType = Countly.Instance.GetDeviceIDType();
+var idType = Countly.Instance.GetDeviceIDType();
if (idType.Equals(Countly.DeviceIdType.DeveloperProvided)) {
// ...do something
}
@@ -500,12 +523,12 @@ if (idType.Equals(Countly.DeviceIdType.DeveloperProvided)) {
base. There are 4 fields that can be provided:
- - Country code (two-letter ISO standard).
- - City name (must be set together with the country code).
- -
+
- Country code (two-letter ISO standard).
+ - City name (must be set together with the country code).
+ -
Latitude and longitude values separated by a comma, e.g. "56.42345,123.45325".
- - Your user’s IP address.
+ - Your user’s IP address.
Setting Location
@@ -517,14 +540,14 @@ if (idType.Equals(Countly.DeviceIdType.DeveloperProvided)) {
During init, you can set location info in the configuration:
-config.SetLocation(countryCode, city, gpsCoordinates, ipAddress);
+config.SetLocation(countryCode, city, gpsCoordinates, ipAddress);
After SDK initialization, this location info will be sent to the server at the
start of the user session. Use SetLocation method to disable or
set the location at any time after the SDK Init call.
For example:
-//set user location
+//set user location
String gpsLocation = "63.445821, 10.898868";
String ipAddress = "13.56.33.12";
String country_code = "us";
@@ -542,7 +565,7 @@ Countly.Instance.SetLocation(gpsLocation, ipAddress, country_code, city);
Users might want to opt-out of location tracking. To do so call:
-//disable location tracking
+//disable location tracking
Countly.Instance.DisableLocation();
This will also erase all location info server side.
User Profiles
@@ -550,7 +573,8 @@ Countly.Instance.DisableLocation();
This feature is available with an
Enterprise Edition subscription.
- For information about User Profiles, review this documentation.
+ For information about User Profiles, review
+ this documentation.
Setting Predefined Values
@@ -559,29 +583,29 @@ Countly.Instance.DisableLocation();
You may set the following predefined data for a particular user:
- -
+
-
Name: Full name of the user.
- -
+
-
Username: Username of the user.
- -
+
-
Email: Email address of the user.
- -
+
-
Organization: Organization the user is working in.
- -
+
-
Phone: Phone number.
- -
+
-
Picture: Web-based Url for the user’s profile.
- -
+
-
Gender: Gender of the user (use only single char like ‘M’
for Male and ‘F’ for Female).
- -
+
-
BirthYear: Birth year of the user.
@@ -594,24 +618,24 @@ Countly.Instance.DisableLocation();
set value as null, you will delete the property.
Example:
-// set name to John
+// set name to John
Countly.UserDetails.Name = "John";
// remove name
Countly.UserDetails.Name = null;
Setting Custom Values
The SDK gives you the flexibility to send only the custom data to Countly servers,
- even when you don’t want to send other user-related data.
You
- can provide custom properties for user using Custom object
+ even when you don’t want to send other user-related data.
+ You can provide custom properties for user using Custom object
-Countly.UserDetails.Custom.Add("city", "london");
+Countly.UserDetails.Custom.Add("city", "london");
Setting User Picture
Additionally, you can upload a picture of the user to the server. Accepted picture
formats are .png, .gif and .jpeg and picture will be resized to maximal 150x150
dimensions.
-Countly.UserDetails.UploadUserPicture(picture_stream);
+Countly.UserDetails.UploadUserPicture(picture_stream);
Note: dots (.) and dollar signs ($) in key names will be stripped
out.
@@ -631,7 +655,7 @@ Countly.UserDetails.Name = null;
do it with the CountlyConfig object by setting consentRequired to
true.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -658,20 +682,22 @@ Countly.Instance.Init(cc);
Features currently supported by this SDK are:
- -
+
-
sessions - tracking when, how often and how long users use your app
- - events - allow sending events to the server
- - location - allow sending location information
- - crashes - allow tracking crashes, exceptions, and errors
- -
+
- events - allow sending events to the server
+ - location - allow sending location information
+ - crashes - allow tracking crashes, exceptions, and errors
+ -
users - allow collecting/providing user information, including custom properties
- In case consent is required, you may give consent to features before the SDK Init call. These features consents are not persistent and must be given on every restart.
+ In case consent is required, you may give consent to features before the SDK
+ Init call. These features consents are not persistent and must be given on every
+ restart.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -694,7 +720,7 @@ Countly.Instance.Init(cc);
Consent can also be changed at any other moment in the app after init:
-//preparing consent features
+//preparing consent features
Dictionary<ConsentFeatures, bool> consent = new Dictionary<ConsentFeatures, bool>();
consent.Add(ConsentFeatures.Crashes, true);
consent.Add(ConsentFeatures.Events, false);
@@ -712,7 +738,7 @@ Countly.Instance.SetConsent(consent);
be checked for the validity of the checksum256 field before being
processed.
-CountlyConfig cc = new CountlyConfig();
+CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
cc.SetParamaterTamperingProtectionSalt("SOME_SALT");
@@ -721,17 +747,21 @@ Countly.Instance.Init(cc);
Other Features and Notes
SDK Config Parameters Explained
- To change the Configuration, update the values of parameters in the "CountlyConfig
- object. Here are the details of the optional parameters:
+ To change the Configuration, update the values of parameters in the "CountlyConfig
+ object. Here are the details of the optional parameters:
- developerProvidedDeviceId - (Optional, string) Your Device ID. It is an optional parameter. Example: f16e5af2-8a2a-4f37-965d-qwer5678ui98.
+ developerProvidedDeviceId - (Optional, string) Your Device ID.
+ It is an optional parameter. Example: f16e5af2-8a2a-4f37-965d-qwer5678ui98.
- consentRequired- (Optional, bool) This is useful during the app run when the user wants to opt-out of SDK features.
+ consentRequired- (Optional, bool) This is useful during the
+ app run when the user wants to opt-out of SDK features.
- sessionUpdateInterval - (Optional, int) Sets the interval (in seconds) after which the application will automatically extend the session. The default value is 60 (seconds).
+ sessionUpdateInterval - (Optional, int) Sets the interval (in
+ seconds) after which the application will automatically extend the session. The
+ default value is 60 (seconds).
AddCustomNetworkRequestHeaders(IDictionary<string, string> customHeaderValues) -
@@ -813,7 +843,7 @@ Countly.Instance.Init(cc);
sent to the Countly server, you can use the AddCustomNetworkRequestHeaders method
during configuration:
-Dictionary<string, string> customHeaderValues = new Dictionary<string, string>();
+Dictionary<string, string> customHeaderValues = new Dictionary<string, string>();
customHeaderValues.Add("foo", "bar");
config.AddCustomNetworkRequestHeaders(customHeaderValues);
@@ -827,9 +857,10 @@ config.AddCustomNetworkRequestHeaders(customHeaderValues);
Key Length
- MaxKeyLength - (int) Maximum size of all string keys. The default value is 128.
+ MaxKeyLength - (int) Maximum size of all string keys. The default
+ value is 128.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -839,9 +870,10 @@ cc.MaxKeyLength = 128;
Countly.Instance.Init(cc);
Value Size
- MaxValueSize - (int) Maximum size of all values in our key-value pairs. The default value is 256.
+ MaxValueSize - (int) Maximum size of all values in our key-value
+ pairs. The default value is 256.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -851,9 +883,10 @@ cc.MaxValueSize = 128;
Countly.Instance.Init(cc);
Segmentation Values
- MaxSegmentationValues - (int) Max amount of custom (dev provided) segmentation in one event. The default value is 100.
+ MaxSegmentationValues - (int) Max amount of custom (dev provided)
+ segmentation in one event. The default value is 100.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -863,9 +896,10 @@ cc.MaxSegmentationValues = 23;
Countly.Instance.Init(cc);
Breadcrumb Count
- MaxBreadcrumbCount - (int)maximum amount of breadcrumbs. The default value is 100.
+ MaxBreadcrumbCount - (int)maximum amount of breadcrumbs. The
+ default value is 100.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -875,9 +909,10 @@ cc.MaxBreadcrumbCount = 50;
Countly.Instance.Init(cc);
Stack Trace Lines Per Thread
- MaxStackTraceLinesPerThread - (int) Limits how many stack trace lines would be recorded per thread. The default value is 30.
+ MaxStackTraceLinesPerThread - (int) Limits how many stack trace
+ lines would be recorded per thread. The default value is 30.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -887,9 +922,10 @@ cc.MaxStackTraceLinesPerThread = 10;
Countly.Instance.Init(cc);
Stack Trace Line Length
- MaxStackTraceLineLength - (int) Limits how many characters are allowed per stack trace line. The default value is 200.
+ MaxStackTraceLineLength - (int) Limits how many characters are
+ allowed per stack trace line. The default value is 200.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -910,7 +946,7 @@ Countly.Instance.Init(cc);
to handle those custom values, they will be ignored.
- IDictionary<string, string> metricOverride = new Dictionary<string, string>();
+ IDictionary<string, string> metricOverride = new Dictionary<string, string>();
metricOverride["SomeKey"] = "123";
metricOverride["_locale"] = "xx_yy";
@@ -928,34 +964,32 @@ Countly.Instance.Init(cc);
For more information on the specific metric keys used by Countly, check
here.
-
- Backend Mode
-
-
- Backend mode allows sending requests with minimal SDK overhead and with the ability to control to which device ID to attribute the recorded data on a per data point level.
-
+Backend Mode
- This feature allows also a fine grain control over to which Countly app the data should be sent.
+ Backend mode allows sending requests with minimal SDK overhead and with the ability
+ to control to which device ID to attribute the recorded data on a per data point
+ level.
- Backend mode is mainly intended for server/backend use cases.
+ This feature allows also a fine grain control over to which Countly app the data
+ should be sent.
- When backend mode is enabled other SDK calls will be ignored.
+ Backend mode is mainly intended for server/backend use cases.
- When in backend mode, nothing is saved persistently and everything is stored only in memory.
+ When backend mode is enabled other SDK calls will be ignored.
- The backend mode does not have checksum ability.
+ When in backend mode, nothing is saved persistently and everything is stored
+ only in memory.
-
- Enabling Backend Mode
-
+The backend mode does not have checksum ability.
+Enabling Backend Mode
- To enable backend mode you need to call "EnableBackendMode" :
+ To enable backend mode you need to call "EnableBackendMode" :
-CountlyConfig cc = new CountlyConfig();
+CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "ONE_OF_YOUR_APP_KEYS";
cc.appVersion = "APP_VERSION";
@@ -965,58 +999,60 @@ await Countly.Instance.Init(cc);
For more information on backend mode configuration options, check bellow.
-
- Recording Data
-
-
- For each call, deviceId parameter is mandatory and should be provided. appKey parameter is optional. However, if multi app recording is intended it should be provided.
-
-
- If app key is not provided, it fallbacks to given app key while initializing.
-
-
- Crash Reporting
-
+Recording Data
- To report a crash with backend mode this method should be called:
+ For each call, deviceId parameter is mandatory and should be provided. appKey
+ parameter is optional. However, if multi app recording is intended it should
+ be provided.
-Countly.Instance.BackendMode().RecordException(string deviceId, string error, string stackTrace = null, IList<string> breadcrumbs = null, IDictionary<string, object> customInfo = null, IDictionary<string, string> metrics = null, bool unhandled = false, string appKey = null, long timestamp = 0);
- For this function to work, only error parameter is required.
+ If app key is not provided, it fallbacks to given app key while initializing.
+Crash Reporting
- Keep in mind that if you want to send data for a device ID or app key that differs from the ones given during the SDK initialization, you must provide them in the function. Here is a minimal call:
+ To report a crash with backend mode this method should be called:
-Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception");
+Countly.Instance.BackendMode().RecordException(string deviceId, string error, string stackTrace = null, IList<string> breadcrumbs = null, IDictionary<string, object> customInfo = null, IDictionary<string, string> metrics = null, bool unhandled = false, string appKey = null, long timestamp = 0);
- Because there is a possibility to multi device recording, metrics also should be provided if metric recording is intended. Here is the supported metric keys:
+ For this function to work, only error parameter is required.
-"_os", "_os_version", "_ram_total", "_ram_current", "_disk_total", "_disk_current", "_online", "_muted", "_resolution", "_app_version", "_manufacture", "_device", "_orientation", "_run"
- Optional values:
+ Keep in mind that if you want to send data for a device ID or app key that differs
+ from the ones given during the SDK initialization, you must provide them in the
+ function. Here is a minimal call:
+Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception");
- - stackTrace: if not provided it will be not sent to the server
+ Because there is a possibility to multi device recording, metrics also should
+ be provided if metric recording is intended. Here is the supported metric keys:
+"_os", "_os_version", "_ram_total", "_ram_current", "_disk_total", "_disk_current", "_online", "_muted", "_resolution", "_app_version", "_manufacture", "_device", "_orientation", "_run"
+Optional values:
- - breadcrumbs: if not provided it will be not sent to the server
+ - stackTrace: if not provided it will be not sent to the server
- - customInfo: custom segmentation of a crash, if not provided it will be not sent to the server. Supported values for the custom info are int, float, double, long, string and bool.
+ - breadcrumbs: if not provided it will be not sent to the server
- - metrics: if not provided it will be not sent to the server
+ - customInfo: custom segmentation of a crash, if not provided
+ it will be not sent to the server. Supported values for the custom info are int,
+ float, double, long, string and bool.
- - unhandled: if not provided it will be recorded as a handled crash. If unhandled crash reporting is intended true value should be passed to the parameter, ex. unhandled: true
+ - metrics: if not provided it will be not sent to the server
- - timestamp: if not provided, it will be set as current timestamp, ex. timestamp: 1703752478530
+ - unhandled: if not provided it will be recorded as a handled
+ crash. If unhandled crash reporting is intended true value should be passed to
+ the parameter, ex. unhandled: true
- Here is a set of examples:
+ - timestamp: if not provided, it will be set as current timestamp,
+ ex. timestamp: 1703752478530
-// unhandled crash reporting with metrics
+Here is a set of examples:
+// unhandled crash reporting with metrics
var metrics = new Dictionary<string, string>(){
{"_os", "Windows"},
{"_os_version", "Windows10NT"},
@@ -1044,17 +1080,15 @@ Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception", stackTrac
// if needed you can also provide timestamp of the exception by adding timestamp to the call, if you do not provide it will be set as current timestamp
Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception", appKey: APP_KEY, timestamp: 1703752478530);
-
- Events
-
+Events
- To record an event with backend mode this method should be called:
+ To record an event with backend mode this method should be called:
-Countly.Instance.BackendMode().RecordEvent(string deviceId, string eventKey, Segmentation segmentations = null, int count = 1, double? sum = null, long? duration = null, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().RecordEvent(string deviceId, string eventKey, Segmentation segmentations = null, int count = 1, double? sum = null, long? duration = null, string appKey = null, long timestamp = 0);
Here are some examples for recording event with the backend mode:
-BackendMode bm = Countly.Instance.BackendMode(); // for convenient calling
+BackendMode bm = Countly.Instance.BackendMode(); // for convenient calling
bm.RecordEvent("device1", "event1", appKey: "app1");
Segmentation segmentation = new Segmentation();
segmentation.Add("uid", "2873673");
@@ -1069,7 +1103,7 @@ bm.RecordEvent("device3", "event3", segmentation, appKey: "app3"); // timestamp
Begin Session
-Countly.Instance.BackendMode().BeginSession(string deviceId, string appKey = null, IDictionary<string, string> metrics = null, IDictionary<string, string> location = null, long timestamp = 0);
+Countly.Instance.BackendMode().BeginSession(string deviceId, string appKey = null, IDictionary<string, string> metrics = null, IDictionary<string, string> location = null, long timestamp = 0);
If no metrics are provided for the BeginSession, it fallbacks to internal metrics
collected from the current device.
@@ -1084,7 +1118,7 @@ bm.RecordEvent("device3", "event3", segmentation, appKey: "app3"); // timestamp
must be provided with the BeginSession method.
Here are examples about BeginSession method.
-// minimal call to the BeginSession, this fallbacks to internal metrics and app key
+// minimal call to the BeginSession, this fallbacks to internal metrics and app key
Countly.Instance.BackendMode().BeginSession(DEVICE_ID);
// With custom metrics, location and custom timestamp (timestamp is optional, if not provided, it will be set as current)
@@ -1111,9 +1145,9 @@ Countly.Instance.BackendMode().BeginSession(DEVICE_ID, APP_KEY, metrics, locatio
Duration is in seconds and required to call update session method.
-Countly.Instance.BackendMode().UpdateSession(string deviceId, int duration, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().UpdateSession(string deviceId, int duration, string appKey = null, long timestamp = 0);
Here are examples about UpdateSession method.
-// minimal call to the UpdateSession, this fallbacks to internal metrics and app key
+// minimal call to the UpdateSession, this fallbacks to internal metrics and app key
Countly.Instance.BackendMode().UpdateSession(DEVICE_ID, 60);
// with custom timestamp
@@ -1124,9 +1158,9 @@ Countly.Instance.BackendMode().UpdateSession(DEVICE_ID, 45, APP_KEY, 17037524785
Duration is in seconds and required. If it is negative, it will be not sent
-Countly.Instance.BackendMode().EndSession(string deviceId, int duration, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().EndSession(string deviceId, int duration, string appKey = null, long timestamp = 0);
Here are examples about EndSession method.
-// minimal call to the EndSession, this fallbacks to internal metrics and app key
+// minimal call to the EndSession, this fallbacks to internal metrics and app key
Countly.Instance.BackendMode().EndSession(DEVICE_ID, -1);
// with custom timestamp and duration
@@ -1140,7 +1174,7 @@ Countly.Instance.BackendMode().EndSession(DEVICE_ID, 45, APP_KEY, 1703752478530)
Start View
-Countly.Instance.BackendMode().StartView(string deviceId, string name, Segmentation segmentations = null, string segment = null, string appKey = null, bool firstView = false, long timestamp = 0);
+Countly.Instance.BackendMode().StartView(string deviceId, string name, Segmentation segmentations = null, string segment = null, string appKey = null, bool firstView = false, long timestamp = 0);
name and segment parameters are required. They should not be empty or null.
@@ -1150,7 +1184,7 @@ Countly.Instance.BackendMode().EndSession(DEVICE_ID, 45, APP_KEY, 1703752478530)
parameter must be provided as true. Default is false.
Here are examples about StartView method.
-// minimal call to the StartView
+// minimal call to the StartView
Countly.Instance.BackendMode().StartView(DEVICE_ID, "Login", segment: "Desktop");
Segmentation segmentation = new Segmentation();
@@ -1162,7 +1196,7 @@ Countly.Instance.BackendMode().StartView(DEVICE_ID, "Login", segmentation, "Desk
Stop View
-Countly.Instance.BackendMode().StopView(string deviceId, string name, long duration, Segmentation segmentations = null, string segment = null, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().StopView(string deviceId, string name, long duration, Segmentation segmentations = null, string segment = null, string appKey = null, long timestamp = 0);
name, segment and duration parameters are required. They should not be empty
or null.
@@ -1170,7 +1204,7 @@ Countly.Instance.BackendMode().StartView(DEVICE_ID, "Login", segmentation, "Desk
Segment is platform for devices or domain for websites.
Duration in seconds and cannot be less then 0
Here are examples about StopView method.
-// minimal call to the StopView
+// minimal call to the StopView
Countly.Instance.BackendMode().StopView(DEVICE_ID, "Logout", 34, segment: "Android");
Segmentation segmentation = new Segmentation();
@@ -1186,10 +1220,10 @@ Countly.Instance.BackendMode().StopView(DEVICE_ID, "Logout", 56, segmentation, "
Change Device ID With Merge
-Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(string newDeviceId, string oldDeviceId, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(string newDeviceId, string oldDeviceId, string appKey = null, long timestamp = 0);
newDeviceId is required, should not be empty or null
Here are examples about ChangeDeviceIdWithMerge method.
-// minimal call to the ChangeDeviceIdWithMerge, this fallbacks to internal app key
+// minimal call to the ChangeDeviceIdWithMerge, this fallbacks to internal app key
Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(NEW_ID, OLD_ID);
// with custom timestamp
@@ -1199,7 +1233,7 @@ Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(NEW_ID, OLD_ID, APP_KEY,
It is possible manage user properties and custom details with the Windows SDK
backend mode.
-Countly.Instance.BackendMode().RecordUserProperties(string deviceId, IDictionary<string, object> userProperties, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().RecordUserProperties(string deviceId, IDictionary<string, object> userProperties, string appKey = null, long timestamp = 0);
userProperties are required and should not be empty. Current supported data types
for the values are: string, int, long, double, float and bool
@@ -1208,12 +1242,12 @@ Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(NEW_ID, OLD_ID, APP_KEY,
Here is the supported predefined keys for user properties. Other than these keys,
everything will be a custom property.
-"name", "username", "email", "organization", "phone", "gender", "byear", "picture"
+"name", "username", "email", "organization", "phone", "gender", "byear", "picture"
To set the picture correctly, only URL of the picture should be provided
Here are examples about RecordUserProperties method.
-// minimal call to the RecordUserProperties, this fallbacks to internal app key
+// minimal call to the RecordUserProperties, this fallbacks to internal app key
var userProperties = new Dictionary<string, object>(){
{"name", "John"},
{"username", "Dohn"},
@@ -1253,100 +1287,91 @@ Countly.Instance.BackendMode().RecordUserProperties(DEVICE_ID, userProperties, A
The keys for predefined modification operations are as follows:
-
-
-
-
- Key
- Description
- Example Usage
-
-
- $inc
-
- increment value by provided value
-
-
- props["age"] = "{$inc: 5}"
-
-
-
- $mul
-
- multiply value by the provided value
-
-
- props["point"] = "{$mul: 1.89}"
-
-
-
- $min
-
- sets minimum value between given and existing
-
-
- props["gpa"] = "{$min: 1.89}"
-
-
-
- $max
-
- sets maximum value between given and existing
-
-
- props["gpa"] = "{$max: 1.89}"
-
-
-
- $setOnce
-
- set value if it does not exist
-
-
- props["name"] = "{$setOnce: \"Name\"}"
-
-
-
- $pull
-
- remove values from an array prop
-
-
- props["permissions"] = "{$pull: [\"Create\", \"Update\"]}"
-
-
-
- $push
-
- insert values to an array prop, same values can be added
-
-
- props["langs"] = "{$push: [\"Python\", \"Ruby\"]}"
-
-
-
- $addToSet
-
- insert values to an array of unique values, same values are ignored
-
-
- props["langs"] = "{$addToSet: [\"Python\", \"Python\"]}"
-
-
-
-
+
+
+
+
+
+ Key
+ Description
+ Example Usage
+
+
+
+
+ $inc
+ increment value by provided value
+
+ props["age"] = "{$inc: 5}"
+
+
+
+ $mul
+ multiply value by the provided value
+
+ props["point"] = "{$mul: 1.89}"
+
+
+
+ $min
+ sets minimum value between given and existing
+
+ props["gpa"] = "{$min: 1.89}"
+
+
+
+ $max
+ sets maximum value between given and existing
+
+ props["gpa"] = "{$max: 1.89}"
+
+
+
+ $setOnce
+ set value if it does not exist
+
+ props["name"] = "{$setOnce: \"Name\"}"
+
+
+
+ $pull
+ remove values from an array prop
+
+ props["permissions"] = "{$pull: [\"Create\", \"Update\"]}"
+
+
+
+ $push
+ insert values to an array prop, same values can be added
+
+ props["langs"] = "{$push: [\"Python\", \"Ruby\"]}"
+
+
+
+ $addToSet
+
+ insert values to an array of unique values, same values are
+ ignored
+
+
+ props["langs"] = "{$addToSet: [\"Python\", \"Python\"]}"
+
+
+
+
+
Direct Requests
The Windows SDK has ability to send direct/custom requests to the server.
-Countly.Instance.BackendMode().RecordDirectRequest(string deviceId, IDictionary<string, string> paramaters, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().RecordDirectRequest(string deviceId, IDictionary<string, string> paramaters, string appKey = null, long timestamp = 0);
Parameters should not be empty
Here are examples about RecordDirectRequest method.
The internal keys are not overridden by the given key values.
-// minimal call to the RecordDirectRequest, this fallbacks to internal app key
+// minimal call to the RecordDirectRequest, this fallbacks to internal app key
var parameters = new Dictionary<string, string>(){
{"begin_session", "1"},
{"metrics", ... }, // metrics to provide
@@ -1384,7 +1409,7 @@ Countly.Instance.BackendMode().RecordDirectRequest(DEVICE_ID, parameters, APP_KE
These limits can be changed with these additional config calls:
-cc.SetMaxRequestQueueSize(1000); // sets request queue max size as 1000
+cc.SetMaxRequestQueueSize(1000); // sets request queue max size as 1000
cc.SetEventQueueSizeToSend(100); // sets event queue size per device
cc.SetBackendModeAppEQSizeToSend(1000): // sets event queue size per app
cc.SetBackendModeServerEQSizeToSend(10000): // sets event queue size for server
@@ -1404,21 +1429,21 @@ cc.SetBackendModeServerEQSizeToSend(10000): // sets event queue size for server<
To catch unhandled exceptions, subscribe to the AppDomain.UnhandledException
event is needed:
-AppDomain.CurrentDomain.UnhandledException += async (sender, args) => {
+AppDomain.CurrentDomain.UnhandledException += async (sender, args) => {
var exception = (Exception)args.ExceptionObject;
await Countly.RecordException(exception.Message, exception.StackTrace, null, true);
};
It is also suggested to subscribe to TaskScheduler.UnobservedTaskException event:
-TaskScheduler.UnobservedTaskException += async (sender, args) => {
+TaskScheduler.UnobservedTaskException += async (sender, args) => {
await Countly.RecordException(args.Exception.Message, args.Exception.StackTrace, null, true);
};
However, some platforms need additional tweaks to handle uncaught exceptions
For Android applications:
-Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += async (sender, args) => {
+Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += async (sender, args) => {
args.Handled = true;
await Countly.RecordException(args.Exception.Message, args.Exception.StackTrace, null, true);
};
@@ -1429,7 +1454,7 @@ cc.SetBackendModeServerEQSizeToSend(10000): // sets event queue size for server<
exception mode to UnwindNativeCode is required to catch exceptions correctly
on iOS/MacCatalyst.
-ObjCRuntime.Runtime.MarshalManagedException += async (_, args) => {
+ObjCRuntime.Runtime.MarshalManagedException += async (_, args) => {
args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode;
};
@@ -1443,15 +1468,26 @@ cc.SetBackendModeServerEQSizeToSend(10000): // sets event queue size for server<
Extended Device ID Management
- In case your application authenticates users, you might want to change the ID to the one in your backend after he has logged in. This helps you identify a specific user with a specific ID on a device he logs in, and the same scenario can also be used in cases this user logs in using a different way (e.g another tablet, another mobile phone, or web). In this case, any data stored in your Countly server database associated with the current device ID will be transferred (merged) into the user profile with the device id you specified in the following method call:
+ In case your application authenticates users, you might want to change the ID
+ to the one in your backend after he has logged in. This helps you identify a
+ specific user with a specific ID on a device he logs in, and the same scenario
+ can also be used in cases this user logs in using a different way (e.g another
+ tablet, another mobile phone, or web). In this case, any data stored in your
+ Countly server database associated with the current device ID will be transferred
+ (merged) into the user profile with the device id you specified in the following
+ method call:
-Countly.Instance.ChangeDeviceId("new-device-id", true);
+Countly.Instance.ChangeDeviceId("new-device-id", true);
- You might want to track information about another separate user that starts using your app (changing apps account), or your app enters a state where you no longer can verify the identity of the current user (user logs out). In that case, you can change the current device ID to a new one without merging their data. You would call:
+ You might want to track information about another separate user that starts using
+ your app (changing apps account), or your app enters a state where you no longer
+ can verify the identity of the current user (user logs out). In that case, you
+ can change the current device ID to a new one without merging their data. You
+ would call:
-Countly.Instance.ChangeDeviceId("new-device-id", false);
+Countly.Instance.ChangeDeviceId("new-device-id", false);
- Doing it this way, will not merge the previously acquired data with the new id.
+ Doing it this way, will not merge the previously acquired data with the new id.
@@ -1459,7 +1495,9 @@ cc.SetBackendModeServerEQSizeToSend(10000): // sets event queue size for server<
- Do note that every time you change your deviceId without a merge, it will be interpreted as a new user. Therefore implementing id management in a bad way could inflate the users count by quite a lot.
+ Do note that every time you change your deviceId without a merge, it will be
+ interpreted as a new user. Therefore implementing id management in a bad way
+ could inflate the users count by quite a lot.
FAQ
What Information Is Collected by the SDK?
@@ -1496,7 +1534,7 @@ cc.SetBackendModeServerEQSizeToSend(10000): // sets event queue size for server<
However, because TLS 1.1 is not supported and TLS 1.2 is forced to be used, the
protocol should be overridden:
-ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
+ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
Before initialization of the Countly Windows SDK, this should be overridden like
above.
From 4533fb7b5282d02e691232f6339257967e47c91b Mon Sep 17 00:00:00 2001
From: Arif Burak Demiray
Date: Tue, 30 Dec 2025 10:54:03 +0300
Subject: [PATCH 3/8] feat: migrate next doc of windows
---
windows/next.md | 682 +++++++++++++++++++++++++-----------------------
1 file changed, 360 insertions(+), 322 deletions(-)
diff --git a/windows/next.md b/windows/next.md
index 3e38faad..dbd7ad9a 100644
--- a/windows/next.md
+++ b/windows/next.md
@@ -15,13 +15,9 @@
The Countly Windows SDK implements the following explicit flavors:
- - .NET Standard 2.0
- -
- .NET Framework 3.5
-
- -
- .NET Framework 4.5
-
+ - .NET Standard 2.0
+ - .NET Framework 3.5
+ - .NET Framework 4.5
@@ -30,19 +26,22 @@
Adding the SDK to the Project
- To install the package, you can use either the NuGet Package Manager or the Package Manager Console. When you install a package, NuGet records the dependency, either in your project file or a packages.config file (depending on the project format).
+ To install the package, you can use either the NuGet Package Manager or the Package
+ Manager Console. When you install a package, NuGet records the dependency, either
+ in your project file or a packages.config file (depending on the
+ project format).
- -
+
-
In Solution Explorer, right-click References and choose
Manage NuGet Packages.
- -
- Choose "nuget.org" as the Package source, select the Browse tab, search for Countly, select that package in the list, and select Install:
-
- -
-
Accept any license prompts.
+ -
+ Choose "nuget.org" as the Package source, select the
+ Browse tab, search for Countly, select
+ that package in the list, and select Install:
+ - Accept any license prompts.
SDK Integration
@@ -50,10 +49,8 @@
Countly.Instance.Init to initiate the SDK.
Minimal Setup
-
- The shortest way to initiate the SDK is with this call:
-
-//create the Countly init object
+The shortest way to initiate the SDK is with this call:
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -62,7 +59,11 @@ cc.appVersion = "1.2.3";
//initiate the SDK with your preferences
Countly.Instance.Init(cc);
- In the CountlyConfig object, you provide appKey and your Countly server URL. Please check here for more information on how to acquire your application key (APP_KEY) and server URL.
+ In the CountlyConfig object, you provide appKey and your Countly
+ server URL. Please check
+ here
+ for more information on how to acquire your application key (APP_KEY) and server
+ URL.
Note: The SDK targets multiple profiles. Therefore for some
@@ -92,14 +93,14 @@ Countly.Instance.Init(cc);
is a call where you can change the path for the named storage folder. You can
change that by using this:
-Countly.SetCustomDataPath("C:\path\to\new\folder\");
+Countly.SetCustomDataPath("C:\path\to\new\folder\");
SDK Notes
Additional Info for UWP Project Setup
It's possible to register an unhandled crash handler during SDK initialization.
To do that, you need to provide a link to your application.
-var cc = new CountlyConfig
+var cc = new CountlyConfig
{
serverUrl = "SERVER_URL",
appKey = "APP_KEY",
@@ -109,21 +110,23 @@ Countly.Instance.Init(cc);
await Countly.Instance.Init(cc);
SDK Logging / Debug Mode
- The first thing you should do while integrating our SDK is to enable logging. If logging is enabled, then our SDK will print out debug messages about its internal state and encounter problems.
- To enable logging you need to do the following two steps:
+ The first thing you should do while integrating our SDK is to enable logging.
+ If logging is enabled, then our SDK will print out debug messages about its internal
+ state and encounter problems. To enable logging you need to do the following
+ two steps:
Step 1: Enable SDK logging using the following call:
-Countly.IsLoggingEnabled = true;
+Countly.IsLoggingEnabled = true;
You can turn it on and off in any place of your code.
- Step 2:
- Go to project properties, select the 'Build' tab and make sure the following things are correct.
+ Step 2: Go to project properties, select the 'Build' tab and
+ make sure the following things are correct.
- - Configuration: Debug
- - "Define DEBUG constant" is checked
+ - Configuration: Debug
+ - "Define DEBUG constant" is checked
@@ -136,7 +139,9 @@ await Countly.Instance.Init(cc);
Crash Reporting
- The Countly SDK for Windows can collect Crash Reports, which you may examine and resolve later on the server.
+ The Countly SDK for Windows can collect
+ Crash Reports,
+ which you may examine and resolve later on the server.
Automatic Crash Handling
@@ -147,10 +152,9 @@ await Countly.Instance.Init(cc);
In that case you would subscribe to that handler and report the received crash
to the SDK with the handled exception method
- RecordException given below by providing
- the unhandled parameter as true. This
- way the crash details and device properties would be saved and sent to the server
- on the next app launch.
+ RecordException given below by providing the
+ unhandled parameter as true. This way the crash details
+ and device properties would be saved and sent to the server on the next app launch.
You can check some platform specific recommendations from
@@ -158,9 +162,12 @@ await Countly.Instance.Init(cc);
Handled Exceptions
- You might catch an exception or similar error during your app’s runtime. You may also log these handled exceptions to monitor how and when they are happening. To log exceptions you can use the RecordException method:
+ You might catch an exception or similar error during your app’s runtime. You
+ may also log these handled exceptions to monitor how and when they are happening.
+ To log exceptions you can use the RecordException
+ method:
-Dictionary<string, string> customInfo = new Dictionary<string, string>{
+Dictionary<string, string> customInfo = new Dictionary<string, string>{
{ "customData", "customValue" }
};
@@ -171,97 +178,105 @@ try {
}
Parameters it takes are:
- -
- error - A string that contains a detailed description
- of the exception.
+
-
+ error - A string that contains a detailed description of
+ the exception.
- -
- stackTrace - A string that describes the contents
- of the call stack.
+
-
+ stackTrace - A string that describes the contents of the
+ call stack.
- -
- customInfo - A Dictionary with string key/value
- pairs to be reported with the crash.
+
-
+ customInfo - A Dictionary with string key/value pairs to
+ be reported with the crash.
- -
+
-
unhandled - A bool value indicating if the crash was fatal
or not.
- If unhandled is set to true the crash report will be saved and sent at the next app start.
+ If unhandled is set to true the crash report will be
+ saved and sent at the next app start.
- If you have handled an exception and it turns out to be fatal to your app, you may use the following shorthand method:
+ If you have handled an exception and it turns out to be fatal to your app, you
+ may use the following shorthand method:
-Countly.RecordUnhandledException(ex.Message, ex.StackTrace);
+Countly.RecordUnhandledException(ex.Message, ex.StackTrace);
Crash Breadcrumbs
- Throughout your app, you can record string values (crash breadcrumbs)
- that could describe previous steps that were taken in your app before
- the crash. After a crash happens, they will be sent together with the crash report.
+ Throughout your app, you can record string values (crash breadcrumbs) that could
+ describe previous steps that were taken in your app before the crash. After a
+ crash happens, they will be sent together with the crash report.
The following command adds a crash breadcrumb:
-Countly.Instance.AddCrashBreadCrumb("breadcrumb");
+Countly.Instance.AddCrashBreadCrumb("breadcrumb");
Consent
- This feature uses Crashes consent. No additional crash logs will be recorded if consent is required and not given.
+ This feature uses Crashes consent. No additional crash logs will
+ be recorded if consent is required and not given.
Events
- An event is any type of action that you can send to a Countly instance, e.g. purchases, changed settings, view enabled, and so on, letting you get valuable information about your application.
+ An event is any type
+ of action that you can send to a Countly instance, e.g. purchases, changed settings,
+ view enabled, and so on, letting you get valuable information about your application.
- There are a couple of values that can be set when recording an event. The main one is the key property which would be the identifier/name for that event. For example, in case a user purchased an item in a game, you could create an event with the key 'purchase'.
+ There are a couple of values that can be set when recording an event. The main
+ one is the key property which would be the identifier/name for
+ that event. For example, in case a user purchased an item in a game, you could
+ create an event with the key 'purchase'.
- Optionally there are also other properties that you might want to set:
+ Optionally there are also other properties that you might want to set:
- -
+
-
Count - a whole numerical value that marks how many times
this event has happened. The default value for that is 1.
- -
+
-
Sum - This value would be summed across all events in the
- dashboard. For example, in-app purchase events sum of purchased items. Its default value is null.
+ dashboard. For example, in-app purchase events sum of purchased items. Its
+ default value is null.
- -
+
-
Duration - Used to record and track the duration of events.
- The default value is null.
+ The default value is null.
- -
+
-
Segmentation- A value where you can provide custom segmentation
for your events to track additional information. It is a key and value map.
- The accepted data types for the value are "String".
+ The accepted data types for the value are "String".
Recording Events
+Here is a quick way to record an event:
+Countly.RecordEvent("event-key");
- Here is a quick way to record an event:
-
-Countly.RecordEvent("event-key");
-
- Based on the example below of an event recording a purchase, here is a quick summary of the information for each usage:
+ Based on the example below of an event recording a purchase,
+ here is a quick summary of the information for each usage:
- -
+
-
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.
@@ -270,15 +285,15 @@ try {
1. Event key and count
-await Countly.RecordEvent("purchase", 3);
+await Countly.RecordEvent("purchase", 3);
2. Event key, count, and sum
-await Countly.RecordEvent("purchase", 3, 0.99);
+await Countly.RecordEvent("purchase", 3, 0.99);
3. Event key and count with segmentation(s)
-Segmentation segmentation = new Segmentation();
+Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");
@@ -287,7 +302,7 @@ await Countly.RecordEvent("purchase", 3, segmentation);
4. Event key, count, and sum with segmentation(s)
-Segmentation segmentation = new Segmentation();
+Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");
@@ -296,13 +311,15 @@ await Countly.RecordEvent("purchase", 3, 2.97, segmentation);
5. Event key, count, sum, duration with segmentation(s)
-Segmentation segmentation = new Segmentation();
+Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");
await Countly.RecordEvent("purchase", 3, 2.97, 122.45, segmentation);
- These are only a few examples of what you can do with Events. You may go beyond those examples and use country, app_version, time_of_day, and any other segmentation of your choice that will provide you with valuable insights.
+ These are only a few examples of what you can do with Events. You may go beyond
+ those examples and use country, app_version, time_of_day, and any other segmentation
+ of your choice that will provide you with valuable insights.
Timed Events
@@ -318,7 +335,7 @@ await Countly.RecordEvent("purchase", 3, 2.97, 122.45, segmentation);
-string eventName = "Some event";
+string eventName = "Some event";
//start some event with the event name "Some event"
Countly.Instance.StartEvent(eventName);
@@ -327,9 +344,11 @@ Countly.Instance.StartEvent(eventName);
//end the event with the same event name "Some event"
Countly.Instance.EndEvent(eventName);
- You may also provide additional information when ending an event. In that case, you can provide the segmentation, count, or sum values. The default values for those are "null", 1, and 0.
+ You may also provide additional information when ending an event. In that case,
+ you can provide the segmentation, count, or sum values. The default values for
+ those are "null", 1, and 0.
-string eventName = "Some event";
+string eventName = "Some event";
//start some event
Countly.Instance.StartEvent(eventName);
@@ -342,13 +361,15 @@ segmentation.Add("wall", "orange");
Countly.Instance.EndEvent(eventName, segmentation);
Here are other options to end timed events:
-//end the event while providing segmentation information and count
-Countly.Instance.EndEvent("timed-event", segmentation, 4);
//end the event while providing segmentation information, count and sum
+//end the event while providing segmentation information and count
+Countly.Instance.EndEvent("timed-event", segmentation, 4);
+
+//end the event while providing segmentation information, count and sum
Countly.Instance.EndEvent("timed-event", segmentation, 4, 10);
You may cancel an already started timed event in case it is not needed anymore:
-//start some event
+//start some event
Countly.Instance.StartEvent(eventName);
//wait some time
@@ -356,10 +377,11 @@ Countly.Instance.StartEvent(eventName);
Countly.Instance.CancelEvent(eventName);
Consent
- This feature uses Events consent. No additional events will be recorded if consent is required and not given.
+ This feature uses Events consent. No additional events will be recorded
+ if consent is required and not given.
- When consent is removed, all previously started timed events will be cancelled.
+ When consent is removed, all previously started timed events will be cancelled.
Sessions
Manual Sessions
@@ -383,7 +405,7 @@ Countly.Instance.CancelEvent(eventName);
Countly.Instance.SessionUpdate(elapsedTime) to track the passage
of time. You should still call it about every minute.
-//start the user session
+//start the user session
Countly.Instance.SessionBegin();
//end the user session
@@ -400,7 +422,7 @@ Countly.Instance.SessionUpdate(elapsedTime);
here. You only need
to provide the name for the view.
-Countly.Instance.RecordView("Some View");
+Countly.Instance.RecordView("Some View");
Device ID Management
To link events, sessions, crashes, etc to a user, a deviceId is used. It is usually
@@ -417,12 +439,12 @@ Countly.Instance.SessionUpdate(elapsedTime);
cons and some limited to a specific compilation target:
- -
+
-
windowsGUID - [all platforms] generates a random GUID that
will be used as a device id. Very high chance of being unique. Will generate
a new id on a reinstall.
- -
+
-
developerSupplied - The device Id was provided by the developer.
Used in cases where developers want to use an id tied to their internal systems/servers.
@@ -431,7 +453,7 @@ Countly.Instance.SessionUpdate(elapsedTime);
Device id and generation method can be provided during SDK init. Those values
can also bet not set, then the default method for that target will be used.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -441,40 +463,41 @@ cc.developerProvidedDeviceId = "use@email.com";
//initiate the SDK with your preferences
Countly.Instance.Init(cc);
Changing Device ID
+You can change the device ID of a user with SetId method:
+await Countly.Instance.SetId("new-device-id");
- You can change the device ID of a user with SetId method:
-
-await Countly.Instance.SetId("new-device-id");
-
- This method's effect on the server will be different according to the type of the current ID stored in the SDK at the time you call it:
+ This method's effect on the server will be different according to the type of
+ the current ID stored in the SDK at the time you call it:
- -
-
- If current stored ID is DeviceIdType.SDKGenerated then in the server all the information recorded for that device ID will be merged to the new ID you provide and old user with the DeviceIdType.SDKGenerated ID will be erased.
-
+ -
+ If current stored ID is
DeviceIdType.SDKGenerated then in the
+ server all the information recorded for that device ID will be merged to
+ the new ID you provide and old user with the
+ DeviceIdType.SDKGenerated ID will be erased.
- -
-
- If the current stored ID is DeviceIdType.DeveloperProvided then in the server it will also create a new user with this new ID if it does not exist.
-
+ -
+ If the current stored ID is
DeviceIdType.DeveloperProvided then
+ in the server it will also create a new user with this new ID if it does
+ not exist.
- NOTE: The call will reject invalid device ID values. A valid value is not null and is not an empty string.
+ NOTE: The call will reject invalid device ID values. A valid value is not null
+ and is not an empty string.
Retrieving Current Device ID
You may want to see what device id Countly is assigning for the specific device.
For that, you may use the following calls.
-string usedId = await Countly.GetDeviceId();
+string usedId = await Countly.GetDeviceId();
SDK record the type of an id. These types are:
- -
+
-
DeveloperProvided
- -
+
-
SDKGenerated
@@ -485,11 +508,11 @@ Countly.Instance.Init(cc);
You can get the device ID type of a user by calling the GetDeviceIDType function:
-var idType = Countly.Instance.GetDeviceIDType();
+var idType = Countly.Instance.GetDeviceIDType();
You can use the DeviceIdType enums to evaluate the device ID type you retrieved:
-var idType = Countly.Instance.GetDeviceIDType();
+var idType = Countly.Instance.GetDeviceIDType();
if (idType.Equals(Countly.DeviceIdType.DeveloperProvided)) {
// ...do something
}
@@ -500,12 +523,12 @@ if (idType.Equals(Countly.DeviceIdType.DeveloperProvided)) {
base. There are 4 fields that can be provided:
- - Country code (two-letter ISO standard).
- - City name (must be set together with the country code).
- -
+
- Country code (two-letter ISO standard).
+ - City name (must be set together with the country code).
+ -
Latitude and longitude values separated by a comma, e.g. "56.42345,123.45325".
- - Your user’s IP address.
+ - Your user’s IP address.
Setting Location
@@ -517,14 +540,14 @@ if (idType.Equals(Countly.DeviceIdType.DeveloperProvided)) {
During init, you can set location info in the configuration:
-config.SetLocation(countryCode, city, gpsCoordinates, ipAddress);
+config.SetLocation(countryCode, city, gpsCoordinates, ipAddress);
After SDK initialization, this location info will be sent to the server at the
start of the user session. Use SetLocation method to disable or
set the location at any time after the SDK Init call.
For example:
-//set user location
+//set user location
String gpsLocation = "63.445821, 10.898868";
String ipAddress = "13.56.33.12";
String country_code = "us";
@@ -542,7 +565,7 @@ Countly.Instance.SetLocation(gpsLocation, ipAddress, country_code, city);
Users might want to opt-out of location tracking. To do so call:
-//disable location tracking
+//disable location tracking
Countly.Instance.DisableLocation();
This will also erase all location info server side.
User Profiles
@@ -550,7 +573,8 @@ Countly.Instance.DisableLocation();
This feature is available with an
Enterprise Edition subscription.
- For information about User Profiles, review this documentation.
+ For information about User Profiles, review
+ this documentation.
Setting Predefined Values
@@ -559,29 +583,29 @@ Countly.Instance.DisableLocation();
You may set the following predefined data for a particular user:
- -
+
-
Name: Full name of the user.
- -
+
-
Username: Username of the user.
- -
+
-
Email: Email address of the user.
- -
+
-
Organization: Organization the user is working in.
- -
+
-
Phone: Phone number.
- -
+
-
Picture: Web-based Url for the user’s profile.
- -
+
-
Gender: Gender of the user (use only single char like ‘M’
for Male and ‘F’ for Female).
- -
+
-
BirthYear: Birth year of the user.
@@ -594,24 +618,24 @@ Countly.Instance.DisableLocation();
set value as null, you will delete the property.
Example:
-// set name to John
+// set name to John
Countly.UserDetails.Name = "John";
// remove name
Countly.UserDetails.Name = null;
Setting Custom Values
The SDK gives you the flexibility to send only the custom data to Countly servers,
- even when you don’t want to send other user-related data.
You
- can provide custom properties for user using Custom object
+ even when you don’t want to send other user-related data.
+ You can provide custom properties for user using Custom object
-Countly.UserDetails.Custom.Add("city", "london");
+Countly.UserDetails.Custom.Add("city", "london");
Setting User Picture
Additionally, you can upload a picture of the user to the server. Accepted picture
formats are .png, .gif and .jpeg and picture will be resized to maximal 150x150
dimensions.
-Countly.UserDetails.UploadUserPicture(picture_stream);
+Countly.UserDetails.UploadUserPicture(picture_stream);
Note: dots (.) and dollar signs ($) in key names will be stripped
out.
@@ -631,7 +655,7 @@ Countly.UserDetails.Name = null;
do it with the CountlyConfig object by setting consentRequired to
true.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -658,20 +682,22 @@ Countly.Instance.Init(cc);
Features currently supported by this SDK are:
- -
+
-
sessions - tracking when, how often and how long users use your app
- - events - allow sending events to the server
- - location - allow sending location information
- - crashes - allow tracking crashes, exceptions, and errors
- -
+
- events - allow sending events to the server
+ - location - allow sending location information
+ - crashes - allow tracking crashes, exceptions, and errors
+ -
users - allow collecting/providing user information, including custom properties
- In case consent is required, you may give consent to features before the SDK Init call. These features consents are not persistent and must be given on every restart.
+ In case consent is required, you may give consent to features before the SDK
+ Init call. These features consents are not persistent and must be given on every
+ restart.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
@@ -694,7 +720,7 @@ Countly.Instance.Init(cc);
Consent can also be changed at any other moment in the app after init:
-//preparing consent features
+//preparing consent features
Dictionary<ConsentFeatures, bool> consent = new Dictionary<ConsentFeatures, bool>();
consent.Add(ConsentFeatures.Crashes, true);
consent.Add(ConsentFeatures.Events, false);
@@ -712,7 +738,7 @@ Countly.Instance.SetConsent(consent);
be checked for the validity of the checksum256 field before being
processed.
-CountlyConfig cc = new CountlyConfig();
+CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "COUNTLY_SERVER_URL";
cc.appKey = "COUNTLY_APP_KEY";
cc.SetParamaterTamperingProtectionSalt("SOME_SALT");
@@ -721,17 +747,21 @@ Countly.Instance.Init(cc);
Other Features and Notes
SDK Config Parameters Explained
- To change the Configuration, update the values of parameters in the "CountlyConfig
- object. Here are the details of the optional parameters:
+ To change the Configuration, update the values of parameters in the "CountlyConfig
+ object. Here are the details of the optional parameters:
- developerProvidedDeviceId - (Optional, string) Your Device ID. It is an optional parameter. Example: f16e5af2-8a2a-4f37-965d-qwer5678ui98.
+ developerProvidedDeviceId - (Optional, string) Your Device ID.
+ It is an optional parameter. Example: f16e5af2-8a2a-4f37-965d-qwer5678ui98.
- consentRequired- (Optional, bool) This is useful during the app run when the user wants to opt-out of SDK features.
+ consentRequired- (Optional, bool) This is useful during the
+ app run when the user wants to opt-out of SDK features.
- sessionUpdateInterval - (Optional, int) Sets the interval (in seconds) after which the application will automatically extend the session. The default value is 60 (seconds).
+ sessionUpdateInterval - (Optional, int) Sets the interval (in
+ seconds) after which the application will automatically extend the session. The
+ default value is 60 (seconds).
AddCustomNetworkRequestHeaders(IDictionary<string, string> customHeaderValues) -
@@ -813,7 +843,7 @@ Countly.Instance.Init(cc);
sent to the Countly server, you can use the AddCustomNetworkRequestHeaders method
during configuration:
-Dictionary<string, string> customHeaderValues = new Dictionary<string, string>();
+Dictionary<string, string> customHeaderValues = new Dictionary<string, string>();
customHeaderValues.Add("foo", "bar");
config.AddCustomNetworkRequestHeaders(customHeaderValues);
@@ -827,9 +857,10 @@ config.AddCustomNetworkRequestHeaders(customHeaderValues);
Key Length
- MaxKeyLength - (int) Maximum size of all string keys. The default value is 128.
+ MaxKeyLength - (int) Maximum size of all string keys. The default
+ value is 128.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -839,9 +870,10 @@ cc.MaxKeyLength = 128;
Countly.Instance.Init(cc);
Value Size
- MaxValueSize - (int) Maximum size of all values in our key-value pairs. The default value is 256.
+ MaxValueSize - (int) Maximum size of all values in our key-value
+ pairs. The default value is 256.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -851,9 +883,10 @@ cc.MaxValueSize = 128;
Countly.Instance.Init(cc);
Segmentation Values
- MaxSegmentationValues - (int) Max amount of custom (dev provided) segmentation in one event. The default value is 100.
+ MaxSegmentationValues - (int) Max amount of custom (dev provided)
+ segmentation in one event. The default value is 100.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -863,9 +896,10 @@ cc.MaxSegmentationValues = 23;
Countly.Instance.Init(cc);
Breadcrumb Count
- MaxBreadcrumbCount - (int)maximum amount of breadcrumbs. The default value is 100.
+ MaxBreadcrumbCount - (int)maximum amount of breadcrumbs. The
+ default value is 100.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -875,9 +909,10 @@ cc.MaxBreadcrumbCount = 50;
Countly.Instance.Init(cc);
Stack Trace Lines Per Thread
- MaxStackTraceLinesPerThread - (int) Limits how many stack trace lines would be recorded per thread. The default value is 30.
+ MaxStackTraceLinesPerThread - (int) Limits how many stack trace
+ lines would be recorded per thread. The default value is 30.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -887,9 +922,10 @@ cc.MaxStackTraceLinesPerThread = 10;
Countly.Instance.Init(cc);
Stack Trace Line Length
- MaxStackTraceLineLength - (int) Limits how many characters are allowed per stack trace line. The default value is 200.
+ MaxStackTraceLineLength - (int) Limits how many characters are
+ allowed per stack trace line. The default value is 200.
-//create the Countly init object
+//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "YOUR_APP_KEY";
@@ -910,7 +946,7 @@ Countly.Instance.Init(cc);
to handle those custom values, they will be ignored.
- IDictionary<string, string> metricOverride = new Dictionary<string, string>();
+ IDictionary<string, string> metricOverride = new Dictionary<string, string>();
metricOverride["SomeKey"] = "123";
metricOverride["_locale"] = "xx_yy";
@@ -928,34 +964,32 @@ Countly.Instance.Init(cc);
For more information on the specific metric keys used by Countly, check
here.
-
- Backend Mode
-
-
- Backend mode allows sending requests with minimal SDK overhead and with the ability to control to which device ID to attribute the recorded data on a per data point level.
-
+Backend Mode
- This feature allows also a fine grain control over to which Countly app the data should be sent.
+ Backend mode allows sending requests with minimal SDK overhead and with the ability
+ to control to which device ID to attribute the recorded data on a per data point
+ level.
- Backend mode is mainly intended for server/backend use cases.
+ This feature allows also a fine grain control over to which Countly app the data
+ should be sent.
- When backend mode is enabled other SDK calls will be ignored.
+ Backend mode is mainly intended for server/backend use cases.
- When in backend mode, nothing is saved persistently and everything is stored only in memory.
+ When backend mode is enabled other SDK calls will be ignored.
- The backend mode does not have checksum ability.
+ When in backend mode, nothing is saved persistently and everything is stored
+ only in memory.
-
- Enabling Backend Mode
-
+The backend mode does not have checksum ability.
+Enabling Backend Mode
- To enable backend mode you need to call "EnableBackendMode" :
+ To enable backend mode you need to call "EnableBackendMode" :
-CountlyConfig cc = new CountlyConfig();
+CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "ONE_OF_YOUR_APP_KEYS";
cc.appVersion = "APP_VERSION";
@@ -965,58 +999,60 @@ await Countly.Instance.Init(cc);
For more information on backend mode configuration options, check bellow.
-
- Recording Data
-
-
- For each call, deviceId parameter is mandatory and should be provided. appKey parameter is optional. However, if multi app recording is intended it should be provided.
-
-
- If app key is not provided, it fallbacks to given app key while initializing.
-
-
- Crash Reporting
-
+Recording Data
- To report a crash with backend mode this method should be called:
+ For each call, deviceId parameter is mandatory and should be provided. appKey
+ parameter is optional. However, if multi app recording is intended it should
+ be provided.
-Countly.Instance.BackendMode().RecordException(string deviceId, string error, string stackTrace = null, IList<string> breadcrumbs = null, IDictionary<string, object> customInfo = null, IDictionary<string, string> metrics = null, bool unhandled = false, string appKey = null, long timestamp = 0);
- For this function to work, only error parameter is required.
+ If app key is not provided, it fallbacks to given app key while initializing.
+Crash Reporting
- Keep in mind that if you want to send data for a device ID or app key that differs from the ones given during the SDK initialization, you must provide them in the function. Here is a minimal call:
+ To report a crash with backend mode this method should be called:
-Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception");
+Countly.Instance.BackendMode().RecordException(string deviceId, string error, string stackTrace = null, IList<string> breadcrumbs = null, IDictionary<string, object> customInfo = null, IDictionary<string, string> metrics = null, bool unhandled = false, string appKey = null, long timestamp = 0);
- Because there is a possibility to multi device recording, metrics also should be provided if metric recording is intended. Here is the supported metric keys:
+ For this function to work, only error parameter is required.
-"_os", "_os_version", "_ram_total", "_ram_current", "_disk_total", "_disk_current", "_online", "_muted", "_resolution", "_app_version", "_manufacture", "_device", "_orientation", "_run"
- Optional values:
+ Keep in mind that if you want to send data for a device ID or app key that differs
+ from the ones given during the SDK initialization, you must provide them in the
+ function. Here is a minimal call:
+Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception");
- - stackTrace: if not provided it will be not sent to the server
+ Because there is a possibility to multi device recording, metrics also should
+ be provided if metric recording is intended. Here is the supported metric keys:
+"_os", "_os_version", "_ram_total", "_ram_current", "_disk_total", "_disk_current", "_online", "_muted", "_resolution", "_app_version", "_manufacture", "_device", "_orientation", "_run"
+Optional values:
- - breadcrumbs: if not provided it will be not sent to the server
+ - stackTrace: if not provided it will be not sent to the server
- - customInfo: custom segmentation of a crash, if not provided it will be not sent to the server. Supported values for the custom info are int, float, double, long, string and bool.
+ - breadcrumbs: if not provided it will be not sent to the server
- - metrics: if not provided it will be not sent to the server
+ - customInfo: custom segmentation of a crash, if not provided
+ it will be not sent to the server. Supported values for the custom info are int,
+ float, double, long, string and bool.
- - unhandled: if not provided it will be recorded as a handled crash. If unhandled crash reporting is intended true value should be passed to the parameter, ex. unhandled: true
+ - metrics: if not provided it will be not sent to the server
- - timestamp: if not provided, it will be set as current timestamp, ex. timestamp: 1703752478530
+ - unhandled: if not provided it will be recorded as a handled
+ crash. If unhandled crash reporting is intended true value should be passed to
+ the parameter, ex. unhandled: true
- Here is a set of examples:
+ - timestamp: if not provided, it will be set as current timestamp,
+ ex. timestamp: 1703752478530
-// unhandled crash reporting with metrics
+Here is a set of examples:
+// unhandled crash reporting with metrics
var metrics = new Dictionary<string, string>(){
{"_os", "Windows"},
{"_os_version", "Windows10NT"},
@@ -1044,17 +1080,15 @@ Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception", stackTrac
// if needed you can also provide timestamp of the exception by adding timestamp to the call, if you do not provide it will be set as current timestamp
Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception", appKey: APP_KEY, timestamp: 1703752478530);
-
- Events
-
+Events
- To record an event with backend mode this method should be called:
+ To record an event with backend mode this method should be called:
-Countly.Instance.BackendMode().RecordEvent(string deviceId, string eventKey, Segmentation segmentations = null, int count = 1, double? sum = null, long? duration = null, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().RecordEvent(string deviceId, string eventKey, Segmentation segmentations = null, int count = 1, double? sum = null, long? duration = null, string appKey = null, long timestamp = 0);
Here are some examples for recording event with the backend mode:
-BackendMode bm = Countly.Instance.BackendMode(); // for convenient calling
+BackendMode bm = Countly.Instance.BackendMode(); // for convenient calling
bm.RecordEvent("device1", "event1", appKey: "app1");
Segmentation segmentation = new Segmentation();
segmentation.Add("uid", "2873673");
@@ -1069,7 +1103,7 @@ bm.RecordEvent("device3", "event3", segmentation, appKey: "app3"); // timestamp
Begin Session
-Countly.Instance.BackendMode().BeginSession(string deviceId, string appKey = null, IDictionary<string, string> metrics = null, IDictionary<string, string> location = null, long timestamp = 0);
+Countly.Instance.BackendMode().BeginSession(string deviceId, string appKey = null, IDictionary<string, string> metrics = null, IDictionary<string, string> location = null, long timestamp = 0);
If no metrics are provided for the BeginSession, it fallbacks to internal metrics
collected from the current device.
@@ -1084,7 +1118,7 @@ bm.RecordEvent("device3", "event3", segmentation, appKey: "app3"); // timestamp
must be provided with the BeginSession method.
Here are examples about BeginSession method.
-// minimal call to the BeginSession, this fallbacks to internal metrics and app key
+// minimal call to the BeginSession, this fallbacks to internal metrics and app key
Countly.Instance.BackendMode().BeginSession(DEVICE_ID);
// With custom metrics, location and custom timestamp (timestamp is optional, if not provided, it will be set as current)
@@ -1111,9 +1145,9 @@ Countly.Instance.BackendMode().BeginSession(DEVICE_ID, APP_KEY, metrics, locatio
Duration is in seconds and required to call update session method.
-Countly.Instance.BackendMode().UpdateSession(string deviceId, int duration, string appKey = null, long timestamp = 0);
+Countly.Instance.BackendMode().UpdateSession(string deviceId, int duration, string appKey = null, long timestamp = 0);
Here are examples about UpdateSession method.
-// minimal call to the UpdateSession, this fallbacks to internal metrics and app key
+// minimal call to the UpdateSession, this fallbacks to internal metrics and app key
Countly.Instance.BackendMode().UpdateSession(DEVICE_ID, 60);
// with custom timestamp
@@ -1124,9 +1158,9 @@ Countly.Instance.BackendMode().UpdateSession(DEVICE_ID, 45, APP_KEY, 17037524785