From ea5118a3aa79bc70297388ddb8d76db0f1605cda Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 23 Dec 2025 10:12:59 +0300 Subject: [PATCH 1/8] feat: beautify java and cpp --- cpp/current.md | 490 +++++++++++++++++---------- cpp/next.md | 281 ++++++++-------- java/current.md | 856 +++++++++++++++++++++++++----------------------- java/next.md | 856 +++++++++++++++++++++++++----------------------- 4 files changed, 1351 insertions(+), 1132 deletions(-) diff --git a/cpp/current.md b/cpp/current.md index fe624e47..14e9180b 100644 --- a/cpp/current.md +++ b/cpp/current.md @@ -1,20 +1,19 @@

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

Click - here, to + here, to access the documentation for older SDK versions.

+

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.

Adding the SDK to the Project

@@ -22,26 +21,26 @@ to run on most platforms. To build this SDK, you need:

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
@@ -50,14 +49,14 @@ 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

In case you would also need to install the built library, check for more information - here. + here.

Build with the option COUNTLY_BUILD_TESTS and @@ -67,9 +66,10 @@ 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
+make ./countly-tests # run unit test +make ./countly-sample # run sample app

SDK Integration

@@ -80,10 +80,12 @@ make ./countly-tests # run unit test
make ./countly-sample # run sample ap

The shortest way to initiate the SDK is with this code snippet:

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

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.

@@ -99,33 +101,9 @@ make ./countly-tests # run unit test
make ./countly-sample # run sample ap

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.

-

SDK Logging

-

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

-

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

-
-
cly::Countly::getInstance().setDeviceID("UNIQUE_DEVICE_ID");
-

SDK Data Storage

In its unconfigured state, the SDK stores everything in memory. @@ -140,7 +118,24 @@ make ./countly-tests # run unit test
make ./countly-sample # run sample ap

To access the Countly Global Instance use the following code snippet:

-
cly::Countly::getInstance().
+
cly::Countly::getInstance()
+

SDK Logging

+

+ 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 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

The Countly SDK for C++ can collect @@ -151,33 +146,49 @@ make ./countly-tests # run unit test
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

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*/
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:

    -
  • +
  • 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.
@@ -191,7 +202,30 @@ make ./countly-tests # run unit test
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*/

Crash Breadcrumbs

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");

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

    -
  • +
  • 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;
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. 

Timed Events

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);
+

Sessions

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

@@ -310,12 +370,19 @@ 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 = {
{"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");
+

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

Setting Location

@@ -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();

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.

@@ -443,6 +522,10 @@ cly::Countly.getInstance().addEvent(event); For information about User Profiles, review this documentation.

+

+ If a property is set as an empty string, it will be deleted from the user on + the server side. +

Setting Predefined Values

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:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
namestringUser's full name
usernamestringUser's nickname
emailstringUser's email address
organizationstringUser's organization name
phonestringUser's phone number
picturestringURL to avatar or profile picture of the user
genderstringUser's gender as M for male and F for female
byearstringUser's year of birth as integer
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescription
namestringUser's full name
usernamestringUser's nickname
emailstringUser's email address
organizationstringUser's organization name
phonestringUser's phone number
picturestringURL to avatar or profile picture of the user
genderstringUser's gender as M for male and F for female
byearstringUser'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);
+

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.

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);
+

Setting User Picture

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);
+

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 @@ -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");

Other Features and Notes

+

SDK Config Parameters Explained

+

+ These are the methods that lets you configure the Countly SDK: +

+ +

Example Integrations

+

+ example_integration.cpp + covers basic functionalities. +

Setting Event Queue Threshold

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

Setting Up SQLite Storage

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.

+

Custom Metrics

+

+ 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);

Setting Custom SHA-256

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

Additional project install option

+
std::string customChecksumCalculator(const std::string& data) {
+  ...
+  return result;
+} 
+
+
+cly::Countly& countly = cly::Countly.getInstance();
+
+countly.setSalt("salt");
+countly.setSha256(customChecksumCalculator);
+
+

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

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.

\ No newline at end of file diff --git a/cpp/next.md b/cpp/next.md index 03ed51f1..14e9180b 100644 --- a/cpp/next.md +++ b/cpp/next.md @@ -6,7 +6,7 @@

Click - here, to + here, to access the documentation for older SDK versions.

@@ -21,26 +21,26 @@ to run on most platforms. To build this SDK, you need:

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

SDK Logging

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:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyTypeDescription
namestringUser's full name
usernamestringUser's nickname
emailstringUser's email address
organizationstringUser's organization name
phonestringUser's phone number
picturestringURL to avatar or profile picture of the user
genderstringUser's gender as M for male and F for female
byearstringUser's year of birth as integer
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescription
namestringUser's full name
usernamestringUser's nickname
emailstringUser's email address
organizationstringUser's organization name
phonestringUser's phone number
picturestringURL to avatar or profile picture of the user
genderstringUser's gender as M for male and F for female
byearstringUser'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.

      @@ -684,21 +693,21 @@ config.enableRemoteConfigAutomaticTriggers(); Omit Keys
      -
      Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
      +    
      Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
         if(requestResult.equals(RequestResult.Success){
           //do sth
         }
       });

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

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    KeyDescription
    $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 -
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyDescription
    $incincrement used value by 1
    $mulmultiply value by the provided value
    $minminimum value
    $maxmaximal value
    $setOnceset value if it does not exist
    $pullremove value from an array
    $pushinsert value to an array
    $addToSetinsert 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.

        @@ -684,21 +693,21 @@ config.enableRemoteConfigAutomaticTriggers(); Omit Keys
        -
        Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
        +    
        Countly.instance().remoteConfig().downloadAllKeys((requestResult, error, fullValueUpdate, downloadedValues) -> {
           if(requestResult.equals(RequestResult.Success){
             //do sth
           }
         });

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

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      KeyDescription
      $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 -
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      KeyDescription
      $incincrement used value by 1
      $mulmultiply value by the provided value
      $minminimum value
      $maxmaximal value
      $setOnceset value if it does not exist
      $pullremove value from an array
      $pushinsert value to an array
      $addToSetinsert 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).

        -
      1. +
      2. In Solution Explorer, right-click References and choose Manage NuGet Packages.image-NuGet-packages.png
      3. -
      4. - Choose "nuget.org" as the Package source, select the Browse tab, search for Countly, select that package in the list, and select Install:mceclip0.png -
      5. -
      6. -

        Accept any license prompts.

        +
      7. + Choose "nuget.org" as the Package source, select the + Browse tab, search for Countly, select + that package in the list, and select Install:mceclip0.png
      8. +
      9. 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

      mceclip1.png @@ -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:

        -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        KeyDescriptionExample 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\"]}" -
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        KeyDescriptionExample Usage
        $incincrement value by provided value + props["age"] = "{$inc: 5}" +
        $mulmultiply value by the provided value + props["point"] = "{$mul: 1.89}" +
        $minsets minimum value between given and existing + props["gpa"] = "{$min: 1.89}" +
        $maxsets maximum value between given and existing + props["gpa"] = "{$max: 1.89}" +
        $setOnceset value if it does not exist + props["name"] = "{$setOnce: \"Name\"}" +
        $pullremove values from an array prop + props["permissions"] = "{$pull: [\"Create\", \"Update\"]}" +
        $pushinsert 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).

          -
        1. +
        2. In Solution Explorer, right-click References and choose Manage NuGet Packages.image-NuGet-packages.png
        3. -
        4. - Choose "nuget.org" as the Package source, select the Browse tab, search for Countly, select that package in the list, and select Install:mceclip0.png -
        5. -
        6. -

          Accept any license prompts.

          +
        7. + Choose "nuget.org" as the Package source, select the + Browse tab, search for Countly, select + that package in the list, and select Install:mceclip0.png
        8. +
        9. 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

        mceclip1.png @@ -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:

          -
          - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          KeyDescriptionExample 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\"]}" -
          +
          +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          KeyDescriptionExample Usage
          $incincrement value by provided value + props["age"] = "{$inc: 5}" +
          $mulmultiply value by the provided value + props["point"] = "{$mul: 1.89}" +
          $minsets minimum value between given and existing + props["gpa"] = "{$min: 1.89}" +
          $maxsets maximum value between given and existing + props["gpa"] = "{$max: 1.89}" +
          $setOnceset value if it does not exist + props["name"] = "{$setOnce: \"Name\"}" +
          $pullremove values from an array prop + props["permissions"] = "{$pull: [\"Create\", \"Update\"]}" +
          $pushinsert 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 d489d27156cf5ccbb948fb77bd85deeeab7f18d9 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Dec 2025 11:06:38 +0300 Subject: [PATCH 4/8] feat: migrate node to new editor --- nodejs/current.md | 313 +++++++++++++++++++++++++--------------------- nodejs/next.md | 311 ++++++++++++++++++++++++--------------------- 2 files changed, 337 insertions(+), 287 deletions(-) diff --git a/nodejs/current.md b/nodejs/current.md index f49cb24b..7d16b8c3 100644 --- a/nodejs/current.md +++ b/nodejs/current.md @@ -13,20 +13,24 @@

          Countly NodeJS runs with the following node versions and up:

          - - - - - - - - - - - - - -
          Node Versions
          ^18^17^16^14.15^12.22
          +
          + + + + + + + + + + + + + + + +
          Node Versions
          ^18^17^16^14.15^12.22
          +

          To examine the example integrations, please have a look here. @@ -44,10 +48,10 @@ yarn

          -
          npm install countly-sdk-nodejs
          +
          npm install countly-sdk-nodejs

          @@ -65,7 +69,7 @@ URL.

          Example basic setup would look like this:

          -
          var Countly = require('countly-sdk-nodejs');
          +
          var Countly = require('countly-sdk-nodejs');
           
           Countly.init({
             app_key: "YOUR-APP-KEY",
          @@ -86,7 +90,7 @@ Countly.init({
             change the location or file name during the initialization by using the storage_path
             flag:
           

          -
          var Countly = require('countly-sdk-nodejs');
          +
          var Countly = require('countly-sdk-nodejs');
           
           Countly.init({
             app_key: "YOUR-APP-KEY",
          @@ -101,7 +105,7 @@ Countly.init({
             do so by setting the debug flag as true, during the init. This way you can see
             the inner workings of the Countly from your console.
           

          -
          var Countly = require('countly-sdk-nodejs');
          +
          var Countly = require('countly-sdk-nodejs');
           
           Countly.init({
             app_key: "YOUR-APP-KEY",
          @@ -113,7 +117,7 @@ Countly.init({
             particular operation. In those situations, you can simply use setLoggingEnabled
             function to turn the logs on or off as you wish, just like this:
           

          -
          //to turn on the logs
          +
          //to turn on the logs
           Countly.setLoggingEnabled(true);
           
           //some code in between
          @@ -129,12 +133,12 @@ Countly.setLoggingEnabled(false);
          To automatically capture and report Javascript errors on your server, call the following function:

          -
          Countly.track_errors()
          +
          Countly.track_errors()

          You can additionally, add more segments or properties/values to track with error reports, by providing an object with key/values to add to error reports.

          -
          Countly.track_errors({
          +
          Countly.track_errors({
             "facebook_sdk": "2.3",
             "jquery": "1.8"
           })
          @@ -148,7 +152,7 @@ Countly.setLoggingEnabled(false);

          Countly.log_error(error, segments);

          -
          try{
          +
          try{
             //do something here
           }
           catch(ex){
          @@ -160,7 +164,7 @@ catch(ex){
             breadcrumbs through out the code, on different actions. This breadcrumb will
             be then combined in single log and reported to server too.
           

          -
          Countly.add_log("user clicked button a");
          +
          Countly.add_log("user clicked button a");

          Events

          Recording Events

          @@ -177,7 +181,7 @@ catch(ex){

          Here is an example of adding an event with all possible properties:

          -
          Countly.add_event({
          +
          Countly.add_event({
             "key": "click",
             "count": 1,
             "sum": 1.5,
          @@ -188,7 +192,9 @@ catch(ex){
             }
           });
          - Data passed should be in UTF-8 +

          + Data passed should be in UTF-8 +

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

          @@ -204,14 +210,14 @@ catch(ex){ First, you can start tracking event time by providing name of the event (which later on will be used as key for event object)

          -
          Countly.start_event("timedEvent")
          +
          Countly.start_event("timedEvent")

          Countly will internally mark the start of event and will wait until you end event with end_event method, setting up dur property based on how much time has passed since start_event for same event name was called.

          -
          //end event
          +
          //end event
           Countly.end_event("timedEvent")
           
           //or end event with additional data
          @@ -234,7 +240,7 @@ Countly.end_event({
             If noHeartBeat is true, then Countly SDK won't extend session
             automatically, and you will need to do that manually.
           

          -
          Countly.begin_session(noHeartBeat);
          +
          Countly.begin_session(noHeartBeat);

          Extending a Session

          By default (if noHeartBeat was not provided in @@ -243,7 +249,7 @@ Countly.end_event({ since last call begin_session or session_duration call, whatever was the last one.

          -
          Countly.session_duration(sec)
          +
          Countly.session_duration(sec)

          Ending a Session

          When visitor is leaving your app or website, you should end his session with @@ -251,30 +257,30 @@ Countly.end_event({ begin session or session_duration calls, whatever was the last one.

          -
          Countly.end_session(sec)
          +
          Countly.end_session(sec)

          View Tracking

          This method allows you to track different parts of your application, called views. You can track how much time is spent on each part of the application.

          -
          Countly.track_view("viewname");
          +
          Countly.track_view("viewname");

          And optionally, as the third parameter, you can provide view segments (key/value pairs) to track together with the view. There is a list of reserved segment keys that should not be used:

            -
          • start
          • -
          • visit
          • -
          • bounce
          • -
          • end
          • -
          • name
          • -
          • domain
          • -
          • view
          • -
          • segment
          • -
          • platform
          • +
          • start
          • +
          • visit
          • +
          • bounce
          • +
          • end
          • +
          • name
          • +
          • domain
          • +
          • view
          • +
          • segment
          • +
          • platform
          -
          //Provide view segments
          +
          //Provide view segments
           Countly.track_view("viewname", {theme:"red", mode:"fullscreen"});

          Device ID Management

          Retrieving Current Device ID

          @@ -282,13 +288,13 @@ Countly.track_view("viewname", {theme:"red", mode:"fullscreen"});
          Countly offers a convenience method (get_device_id) for you to get the current user's device ID:

          -
          var id = Countly.get_device_id();
          +
          var id = Countly.get_device_id();

          SDK records the type of an ID. These types are:

            -
          • +
          • DEVELOPER_SUPPLIED
          • -
          • +
          • SDK_GENERATED
          @@ -301,47 +307,48 @@ Countly.track_view("viewname", {theme:"red", mode:"fullscreen"});
          You can get the device ID type of a user by calling the get_device_id_type function:

          -
          var idType = Countly.get_device_id_type();
          +
          var idType = Countly.get_device_id_type();

          You can use the DeviceIdType enums to evaluate the device ID type you retrieved:

          -
          var idType = Countly.get_device_id_type();
          +
          var idType = Countly.get_device_id_type();
           if (idType === Countly.DeviceIdType.SDK_GENERATED) {
             // ...do something
           }
           

          Changing Device ID

          +

          You can change the device ID of a user with set_id method:

          +
          Countly.set_id("newId");

          - You can change the device ID of a user with set_id method: -

          -
          Countly.set_id("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 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 SDK_GENERATED ID will be erased. -

            +
          • + If current stored ID is 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 SDK_GENERATED ID will be erased.
          • -
          • -

            - If the current stored ID is 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 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.10.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.10.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, not undefined, of type string and is not an empty string. + NOTE: The call will reject invalid device ID values. A valid value is not null, + not undefined, of type string and is not an empty string.

          Remote Config

          - Remote Config feature enables you to fetch data that you have created in your server. Depending on the conditions you have set, you can fetch data from your server for the specific users that fits those conditions and process the Remote Config data in anyway you want. Whether to change the background color of your site to showing a certain message, the possibilities are virtually endless. For more information on Remote Config please check here. + Remote Config feature enables you to fetch data that you have created in your server. Depending on the conditions you have set, you can fetch data from your server for the specific users that fits those conditions and process the Remote Config data in anyway you want. Whether to change the background color of your site to showing a certain message, the possibilities are virtually endless. For more information on Remote Config please check here.

          Automatic Remote Config

          @@ -353,17 +360,21 @@ if (idType === Countly.DeviceIdType.SDK_GENERATED) {

          If you provide a callback, the callback will be called when the Remote Config is initially loaded and when it is reloaded if you change the device_id. This callback should have two parameters, first is for error, and second is for the Remote Config object.

          -
          // in your Countly init script
          +
          // in your Countly init script
           Countly.init({
             app_key:"YOUR_APP_KEY",
          -  url: "https://try.count.ly",
          debug: true, + url: "https://try.count.ly", + debug: true, remote_config: true -});

          // OR +}); + +// OR // provide a callback to be notified when configs are loaded Countly.init({ app_key:"YOUR_APP_KEY", - url: "https://try.count.ly",
          debug: true, + url: "https://try.count.ly", + debug: true, remote_config: function(err, remoteConfigs){ if (!err) { //we have our remoteConfigs here @@ -384,11 +395,15 @@ Countly.init({ is the recommended practice. This callback should have two parameters, first is for error, and second is for the Remote Config object.

          -
          // load the whole configuration object with a callback
          +
          // load the whole configuration object with a callback
           Countly.fetch_remote_config(function(err, remoteConfigs){
             if (!err) {
          -    console.log(remoteConfigs);
          // or do something else here if you want with remoteConfigs object - }
          });

          // or whole configuration object with no params + console.log(remoteConfigs); + // or do something else here if you want with remoteConfigs object + } +}); + +// or whole configuration object with no params Countly.fetch_remote_config();

          Fetch Specific Keys

          @@ -397,12 +412,15 @@ Countly.fetch_remote_config();

          a second parameter. This callback should have two parameters, first is for error, and second is for the Remote Config object.

          -
          // load specific keys only, as `key1` and `key2`
          +
          // load specific keys only, as `key1` and `key2`
           Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){
             if (!err) {
          -    console.log(remoteConfigs);
          // or do something else here if you want with remoteConfigs object + console.log(remoteConfigs); + // or do something else here if you want with remoteConfigs object } -});

          +}); + +

          Fetch All Except Specific Keys

          Here the first parameter should be set to 'null' or 'undefined' and the keys @@ -410,10 +428,11 @@ Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){ keys as string. As a third parameter you can provide a callback function. This callback should have two parameters, first is for error, and second is for the Remote Config object.

          -
          // load all key values except specific keys, as `key1` and `key2'
          +
          // load all key values except specific keys, as `key1` and `key2'
           Countly.fetch_remote_config(null, ["key1","key2"], function(err, remoteConfigs){
             if (!err) {
          -    console.log(remoteConfigs);
          // or do something else here if you want with remoteConfigs object + console.log(remoteConfigs); + // or do something else here if you want with remoteConfigs object } });

          Accessing Remote Config Values

          @@ -423,16 +442,16 @@ Countly.fetch_remote_config(null, ["key1","key2"], function(err, remoteConfigs){

          This method should be called once the Remote Config have been successfully loaded, or it will simply return an empty object or undefined values.

          -
          //get whole Remote Config object
          +
          //get whole Remote Config object
           var remoteConfig = Countly.get_remote_config();
           
           //or get value for specific key like 'test'
          -var test = Countly.get_remote_config("test");
          +var test = Countly.get_remote_config("test");

          A/B Testing

          While fetching Remote Config, the SDK will automatically enroll the user to A/B testing. For more information on A/B testing please check - here. + here.

          Consent

          @@ -444,7 +463,7 @@ var test = Countly.get_remote_config("test"); -

          //user feedback
          +
          //user feedback
           Countly.report_feedback({
             widget_id:"1234567890",
             contactMe: true,
          @@ -471,7 +490,7 @@ Countly.report_feedback({
             If a parameter is set as an empty string, it will be deleted on the server side.
           

          The list of possible parameters you can pass is:

          -
          Countly.user_details({
          +
          Countly.user_details({
             "name": "Arturs Sosins",
             "username": "ar2rsawseen",
             "email": "test@test.com",
          @@ -494,7 +513,7 @@ Countly.report_feedback({
             property.
           

          Below is the list of available methods:

          -
          Countly.userData.set(key, value) //set custom property
          +
          Countly.userData.set(key, value) //set custom property
           Countly.userData.set_once(key, value) //set custom property only if property does not exist
           Countly.userData.increment(key) //increment value in key by one
           Countly.userData.increment_by(key, value) //increment value in key by provided value
          @@ -511,7 +530,7 @@ Countly.userData.save() //send userData to server
          it depend on which trace you report.

          Here is an example of how to report network trace:

          -
          //report network trace
          +
          //report network trace
           Countly.report_trace({
             type: "network", //device or network
             name: "/some/endpoint", //use name to identify trace and group them by
          @@ -525,7 +544,7 @@ Countly.report_trace({
             }
           });

           And here is an example of device trace:

          -
          //user built in method to report app start time
          +
          //user built in method to report app start time
           Countly.report_app_start();
           
           //or report device trace manually
          @@ -539,7 +558,7 @@ Countly.report_trace({
             }
           });

          Or you can report any custom traces to provide duration:

          -
          //or report device trace manually
          +
          //or report device trace manually
           Countly.report_trace({
             type: "device", //device or network
             name: "Some process we launched", //use name to identify trace and group them by
          @@ -555,82 +574,88 @@ Countly.report_trace({
             Here are the properties you can setup on Countly initialization
           

            -
          • +
          • app_key - Mandatory! The App Key for your app which created in Countly.
          • -
          • +
          • url - Mandatory! Countly server URL. You must use your server URL here!
          • -
          • +
          • device_id - To identify a visitor, SDK will auto-generate if not provided!
          • -
          • +
          • app_version - Version of your app or website.
          • -
          • +
          • country_code - Country code for your visitor.
          • -
          • +
          • city - Name of the city of your visitor.
          • -
          • +
          • ip_address - IP address of your visitor.
          • -
          • +
          • debug - Output debug info into the console (default: false).
          • -
          • +
          • interval - Set an interval how often to check if there is any data to report and report it (default: 500 ms).
          • -
          • +
          • fail_timeout - Set time in seconds to wait after a failed connection to the server (default: 60 seconds).
          • -
          • +
          • session_update - How often in seconds should session be extended (default: 60 seconds).
          • -
          • +
          • max_events - Maximum amount of events to send in one batch (default: 10).
          • -
          • +
          • force_post - Force using the post method for all requests (default: false)
          • -
          • +
          • storage_path - Where SDK would store data, including id, queues, etc. (default: "../data/").
          • -
          • - storage_type - Determines which storage type will be applied. - Built-in storage type options are: +
          • +

            + storage_type - Determines which storage type will be + applied. Built-in storage type options are: +

              -
            • +
            • File Storage: Countly.StorageTypes.FILE
            • -
            • +
            • Memory Storage: Countly.StorageTypes.MEMORY
            - If custom_storage_method will be used, do not use the storage type. If the - user didn't set the storage type, SDK applies the default. Default is: - Countly.StorageTypes.FILE +

            + If custom_storage_method will be used, do not use the storage type. If + the user didn't set the storage type, SDK applies the default. Default + is: Countly.StorageTypes.FILE +

          • -
          • - custom_storage_method - User-given storage methods that - can be used instead of the default File Storage or the optional Memory Storage - methods. +
          • +

            + custom_storage_method - User-given storage methods that + can be used instead of the default File Storage or the optional Memory + Storage methods. +

              -
            • +
            • If no storage_path is provided with the custom method, the storage path will be the default path! (default: "../data/")
            • -
            • +
            • The object must contain storeGet, storeSet, and storeRemove functions!
            @@ -642,24 +667,24 @@ Countly.report_trace({

        • -
        • +
        • require_consent - pass true if you are implementing GDPR compatible consent management. It would prevent running any functionality without proper consent (default: false).
        • -
        • +
        • remote_config - Enable automatic remote config fetching, provide callback function to be notified when fetching done (default: false).
        • -
        • +
        • http_options - Function to get http options by reference and overwrite them, before running each request.
        • -
        • +
        • max_breadcrumb_count - Maximum amount of breadcrumbs to store for crash logs (default: 100)
        • -
        • +
        • metrics - Provide metrics override or custom metrics for this user. For more information on the specific metric keys used by Countly, check @@ -667,7 +692,7 @@ Countly.report_trace({

        Setting up properties in Countly NodeJS SDK is as follows

        -
        Countly.init({
        +
        Countly.init({
           debug:false,
           app_key:"YOUR_APP_KEY",
           device_id:"1234-1234-1234-1234",
        @@ -712,7 +737,7 @@ Countly.report_trace({
           max_key_length - 128 chars by default. Keys that
           exceed this limit will be truncated.
         

        -
        Countly.init({
        +
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "YOUR_SERVER_URL",
           max_key_length: 50
        @@ -722,7 +747,7 @@ Countly.report_trace({
           max_value_size - 256 chars by default. Values
           that exceed this limit will be truncated.
         

        -
        Countly.init({
        +
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "YOUR_SERVER_URL",
           max_value_size: 12
        @@ -732,7 +757,7 @@ Countly.report_trace({
           max_segmentation_values - 100 dev entries by
           default. Key/value pairs that exceed this limit in a single event will be removed.
         

        -
        Countly.init({
        +
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "YOUR_SERVER_URL",
           max_segmentation_values: 67
        @@ -742,7 +767,7 @@ Countly.report_trace({
           max_breadcrumb_count - 100 entries by default.
           If the limit is exceeded, the oldest entry will be removed from stored breadcrumbs.
         

        -
        Countly.init({
        +
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "YOUR_SERVER_URL",
           max_breadcrumb_count: 45
        @@ -753,7 +778,7 @@ Countly.report_trace({
           default. Crash stack trace lines that exceed this limit (per thread) will be
           removed.
         

        -
        Countly.init({
        +
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "YOUR_SERVER_URL",
           max_stack_trace_lines_per_thread: 23
        @@ -763,7 +788,7 @@ Countly.report_trace({
           max_stack_trace_line_length - 200 chars by default.
           Crash stack trace lines that exceed this limit will be truncated.
         

        -
        Countly.init({
        +
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "YOUR_SERVER_URL",
           max_stack_trace_line_length: 10
        @@ -792,7 +817,7 @@ Countly.report_trace({
           1,000 will be used.
         

        -
        Countly.init({
        +  
        Countly.init({
           app_key:"YOUR_APP_KEY",
           url: "https://try.count.ly",
           queueSize: 5000
        @@ -807,7 +832,7 @@ Countly.report_trace({
           Note: that conversion for each user may be reported only once, all other conversions
           will be ignored for this same user
         

        -
        //or provide campaign id yourself
        +
        //or provide campaign id yourself
         Countly.report_conversion("MyCampaignID");

        Make Direct Request

        @@ -816,7 +841,7 @@ Countly.report_conversion("MyCampaignID");

        with all possible SDK parameters described in API reference

        -
        Countly.request({
        +
        Countly.request({
           app_key:"somekey", 
           devide_id:"someid", 
           events:"[{'key':'val','count':1}]", 
        @@ -841,11 +866,11 @@ Countly.report_conversion("MyCampaignID");

        While evaluating objects, SDK checks for, if:

          -
        • an object is provided
        • -
        • +
        • an object is provided
        • +
        • that object has the valid keys (storeSet, storeGet, storeRemove)
        • -
        • the values are functions
        • +
        • the values are functions

        In cases where an invalid object is provided as the custom method, SDK will ignore @@ -870,47 +895,47 @@ Countly.report_conversion("MyCampaignID");

        Custom Storage Object must contain the 3 main methods SDK uses internally;

          -
        • +
        • storeSet(key, value, callback) - must save the provided key/value pair into the storage. Callback is a function that is invoked to indicate the result of the operation after the operation is complete or failed. Parameters:
            -
          • +
          • key(string) - the key to associate with the value.
          • -
          • +
          • value(null | boolean | number | string | object) - the value to store (can be null, primitive, or an object).
          • -
          • +
          • callback(function) - after the value is stored under the given key, you should call the callback if given, with the error object of the failed operation or with null if the operation succeeds.
        • -
        • +
        • storeGet(key, def) - must return the value for the provided key. If the value for the key does not exist, it should return the def value. Parameters:
            -
          • +
          • key(string) - the key that's associated with the value.
          • -
          • +
          • def(null | boolean | number | string | object) - default value to use if it doesn't set (can be null, primitive, or an object).
        • -
        • +
        • storeRemove(key) - must remove the key and value from the - storage for the provided key. Parameters:
          + storage for the provided key. Parameters:
            -
          • +
          • key(string) - the key that's associated with the value to remove.
          • @@ -918,7 +943,7 @@ Countly.report_conversion("MyCampaignID");

      Custom Storage Method object should be like this:

      -
      const customStorageMethods = {
      +
      const customStorageMethods = {
           /**
            * @example
            * customStorageMethods.storeSet('myKey', 'myValue', function(error) {
      @@ -953,7 +978,7 @@ Countly.report_conversion("MyCampaignID");
      In some cases you may want to change the ID of the user/device that you provided or Countly generated automatically, for example, when user was changed.

      -
      Countly.change_id("myNewId");
      +
      Countly.change_id("myNewId");

      If device ID is changed without merging and consent was enabled, all previously given consent will be removed. This means that all features will cease to function until new consent has been given again for that new device ID. @@ -970,11 +995,11 @@ Countly.report_conversion("MyCampaignID");

      This call will merge any data recorded for current ID and save it as user with new provided ID.

      -
      Countly.change_id("myNewId", true);
      +
      Countly.change_id("myNewId", true);

      FAQ

      What Information is Collected by the SDK?

      The data that SDKs gather to carry out their tasks and implement the necessary functionalities is mentioned here -

      +

      \ No newline at end of file diff --git a/nodejs/next.md b/nodejs/next.md index fa1a4b27..7d16b8c3 100644 --- a/nodejs/next.md +++ b/nodejs/next.md @@ -13,20 +13,24 @@

      Countly NodeJS runs with the following node versions and up:

      - - - - - - - - - - - - - -
      Node Versions
      ^18^17^16^14.15^12.22
      +
      + + + + + + + + + + + + + + + +
      Node Versions
      ^18^17^16^14.15^12.22
      +

      To examine the example integrations, please have a look here. @@ -44,10 +48,10 @@ yarn

    -
    npm install countly-sdk-nodejs
    +
    npm install countly-sdk-nodejs

    @@ -65,7 +69,7 @@ URL.

    Example basic setup would look like this:

    -
    var Countly = require('countly-sdk-nodejs');
    +
    var Countly = require('countly-sdk-nodejs');
     
     Countly.init({
       app_key: "YOUR-APP-KEY",
    @@ -86,7 +90,7 @@ Countly.init({
       change the location or file name during the initialization by using the storage_path
       flag:
     

    -
    var Countly = require('countly-sdk-nodejs');
    +
    var Countly = require('countly-sdk-nodejs');
     
     Countly.init({
       app_key: "YOUR-APP-KEY",
    @@ -101,7 +105,7 @@ Countly.init({
       do so by setting the debug flag as true, during the init. This way you can see
       the inner workings of the Countly from your console.
     

    -
    var Countly = require('countly-sdk-nodejs');
    +
    var Countly = require('countly-sdk-nodejs');
     
     Countly.init({
       app_key: "YOUR-APP-KEY",
    @@ -113,7 +117,7 @@ Countly.init({
       particular operation. In those situations, you can simply use setLoggingEnabled
       function to turn the logs on or off as you wish, just like this:
     

    -
    //to turn on the logs
    +
    //to turn on the logs
     Countly.setLoggingEnabled(true);
     
     //some code in between
    @@ -129,12 +133,12 @@ Countly.setLoggingEnabled(false);
    To automatically capture and report Javascript errors on your server, call the following function:

    -
    Countly.track_errors()
    +
    Countly.track_errors()

    You can additionally, add more segments or properties/values to track with error reports, by providing an object with key/values to add to error reports.

    -
    Countly.track_errors({
    +
    Countly.track_errors({
       "facebook_sdk": "2.3",
       "jquery": "1.8"
     })
    @@ -148,7 +152,7 @@ Countly.setLoggingEnabled(false);

    Countly.log_error(error, segments);

    -
    try{
    +
    try{
       //do something here
     }
     catch(ex){
    @@ -160,7 +164,7 @@ catch(ex){
       breadcrumbs through out the code, on different actions. This breadcrumb will
       be then combined in single log and reported to server too.
     

    -
    Countly.add_log("user clicked button a");
    +
    Countly.add_log("user clicked button a");

    Events

    Recording Events

    @@ -177,7 +181,7 @@ catch(ex){

    Here is an example of adding an event with all possible properties:

    -
    Countly.add_event({
    +
    Countly.add_event({
       "key": "click",
       "count": 1,
       "sum": 1.5,
    @@ -188,7 +192,9 @@ catch(ex){
       }
     });
    - Data passed should be in UTF-8 +

    + Data passed should be in UTF-8 +

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

    @@ -204,14 +210,14 @@ catch(ex){ First, you can start tracking event time by providing name of the event (which later on will be used as key for event object)

    -
    Countly.start_event("timedEvent")
    +
    Countly.start_event("timedEvent")

    Countly will internally mark the start of event and will wait until you end event with end_event method, setting up dur property based on how much time has passed since start_event for same event name was called.

    -
    //end event
    +
    //end event
     Countly.end_event("timedEvent")
     
     //or end event with additional data
    @@ -234,7 +240,7 @@ Countly.end_event({
       If noHeartBeat is true, then Countly SDK won't extend session
       automatically, and you will need to do that manually.
     

    -
    Countly.begin_session(noHeartBeat);
    +
    Countly.begin_session(noHeartBeat);

    Extending a Session

    By default (if noHeartBeat was not provided in @@ -243,7 +249,7 @@ Countly.end_event({ since last call begin_session or session_duration call, whatever was the last one.

    -
    Countly.session_duration(sec)
    +
    Countly.session_duration(sec)

    Ending a Session

    When visitor is leaving your app or website, you should end his session with @@ -251,30 +257,30 @@ Countly.end_event({ begin session or session_duration calls, whatever was the last one.

    -
    Countly.end_session(sec)
    +
    Countly.end_session(sec)

    View Tracking

    This method allows you to track different parts of your application, called views. You can track how much time is spent on each part of the application.

    -
    Countly.track_view("viewname");
    +
    Countly.track_view("viewname");

    And optionally, as the third parameter, you can provide view segments (key/value pairs) to track together with the view. There is a list of reserved segment keys that should not be used:

      -
    • start
    • -
    • visit
    • -
    • bounce
    • -
    • end
    • -
    • name
    • -
    • domain
    • -
    • view
    • -
    • segment
    • -
    • platform
    • +
    • start
    • +
    • visit
    • +
    • bounce
    • +
    • end
    • +
    • name
    • +
    • domain
    • +
    • view
    • +
    • segment
    • +
    • platform
    -
    //Provide view segments
    +
    //Provide view segments
     Countly.track_view("viewname", {theme:"red", mode:"fullscreen"});

    Device ID Management

    Retrieving Current Device ID

    @@ -282,13 +288,13 @@ Countly.track_view("viewname", {theme:"red", mode:"fullscreen"});
    Countly offers a convenience method (get_device_id) for you to get the current user's device ID:

    -
    var id = Countly.get_device_id();
    +
    var id = Countly.get_device_id();

    SDK records the type of an ID. These types are:

      -
    • +
    • DEVELOPER_SUPPLIED
    • -
    • +
    • SDK_GENERATED
    @@ -301,47 +307,48 @@ Countly.track_view("viewname", {theme:"red", mode:"fullscreen"});
    You can get the device ID type of a user by calling the get_device_id_type function:

    -
    var idType = Countly.get_device_id_type();
    +
    var idType = Countly.get_device_id_type();

    You can use the DeviceIdType enums to evaluate the device ID type you retrieved:

    -
    var idType = Countly.get_device_id_type();
    +
    var idType = Countly.get_device_id_type();
     if (idType === Countly.DeviceIdType.SDK_GENERATED) {
       // ...do something
     }
     

    Changing Device ID

    +

    You can change the device ID of a user with set_id method:

    +
    Countly.set_id("newId");

    - You can change the device ID of a user with set_id method: -

    -
    Countly.set_id("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 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 SDK_GENERATED ID will be erased. -

      +
    • + If current stored ID is 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 SDK_GENERATED ID will be erased.
    • -
    • -

      - If the current stored ID is 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 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.10.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.10.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, not undefined, of type string and is not an empty string. + NOTE: The call will reject invalid device ID values. A valid value is not null, + not undefined, of type string and is not an empty string.

    Remote Config

    - Remote Config feature enables you to fetch data that you have created in your server. Depending on the conditions you have set, you can fetch data from your server for the specific users that fits those conditions and process the Remote Config data in anyway you want. Whether to change the background color of your site to showing a certain message, the possibilities are virtually endless. For more information on Remote Config please check here. + Remote Config feature enables you to fetch data that you have created in your server. Depending on the conditions you have set, you can fetch data from your server for the specific users that fits those conditions and process the Remote Config data in anyway you want. Whether to change the background color of your site to showing a certain message, the possibilities are virtually endless. For more information on Remote Config please check here.

    Automatic Remote Config

    @@ -353,17 +360,21 @@ if (idType === Countly.DeviceIdType.SDK_GENERATED) {

    If you provide a callback, the callback will be called when the Remote Config is initially loaded and when it is reloaded if you change the device_id. This callback should have two parameters, first is for error, and second is for the Remote Config object.

    -
    // in your Countly init script
    +
    // in your Countly init script
     Countly.init({
       app_key:"YOUR_APP_KEY",
    -  url: "https://try.count.ly",
    debug: true, + url: "https://try.count.ly", + debug: true, remote_config: true -});

    // OR +}); + +// OR // provide a callback to be notified when configs are loaded Countly.init({ app_key:"YOUR_APP_KEY", - url: "https://try.count.ly",
    debug: true, + url: "https://try.count.ly", + debug: true, remote_config: function(err, remoteConfigs){ if (!err) { //we have our remoteConfigs here @@ -384,11 +395,15 @@ Countly.init({ is the recommended practice. This callback should have two parameters, first is for error, and second is for the Remote Config object.

    -
    // load the whole configuration object with a callback
    +
    // load the whole configuration object with a callback
     Countly.fetch_remote_config(function(err, remoteConfigs){
       if (!err) {
    -    console.log(remoteConfigs);
    // or do something else here if you want with remoteConfigs object - }
    });

    // or whole configuration object with no params + console.log(remoteConfigs); + // or do something else here if you want with remoteConfigs object + } +}); + +// or whole configuration object with no params Countly.fetch_remote_config();

    Fetch Specific Keys

    @@ -397,12 +412,15 @@ Countly.fetch_remote_config();

    a second parameter. This callback should have two parameters, first is for error, and second is for the Remote Config object.

    -
    // load specific keys only, as `key1` and `key2`
    +
    // load specific keys only, as `key1` and `key2`
     Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){
       if (!err) {
    -    console.log(remoteConfigs);
    // or do something else here if you want with remoteConfigs object + console.log(remoteConfigs); + // or do something else here if you want with remoteConfigs object } -});

    +}); + +

    Fetch All Except Specific Keys

    Here the first parameter should be set to 'null' or 'undefined' and the keys @@ -410,10 +428,11 @@ Countly.fetch_remote_config(["key1","key2"], function(err, remoteConfigs){ keys as string. As a third parameter you can provide a callback function. This callback should have two parameters, first is for error, and second is for the Remote Config object.

    -
    // load all key values except specific keys, as `key1` and `key2'
    +
    // load all key values except specific keys, as `key1` and `key2'
     Countly.fetch_remote_config(null, ["key1","key2"], function(err, remoteConfigs){
       if (!err) {
    -    console.log(remoteConfigs);
    // or do something else here if you want with remoteConfigs object + console.log(remoteConfigs); + // or do something else here if you want with remoteConfigs object } });

    Accessing Remote Config Values

    @@ -423,16 +442,16 @@ Countly.fetch_remote_config(null, ["key1","key2"], function(err, remoteConfigs){

    This method should be called once the Remote Config have been successfully loaded, or it will simply return an empty object or undefined values.

    -
    //get whole Remote Config object
    +
    //get whole Remote Config object
     var remoteConfig = Countly.get_remote_config();
     
     //or get value for specific key like 'test'
    -var test = Countly.get_remote_config("test");
    +var test = Countly.get_remote_config("test");

    A/B Testing

    While fetching Remote Config, the SDK will automatically enroll the user to A/B testing. For more information on A/B testing please check - here. + here.

    Consent

    @@ -444,7 +463,7 @@ var test = Countly.get_remote_config("test"); -

    //user feedback
    +
    //user feedback
     Countly.report_feedback({
       widget_id:"1234567890",
       contactMe: true,
    @@ -471,7 +490,7 @@ Countly.report_feedback({
       If a parameter is set as an empty string, it will be deleted on the server side.
     

    The list of possible parameters you can pass is:

    -
    Countly.user_details({
    +
    Countly.user_details({
       "name": "Arturs Sosins",
       "username": "ar2rsawseen",
       "email": "test@test.com",
    @@ -494,7 +513,7 @@ Countly.report_feedback({
       property.
     

    Below is the list of available methods:

    -
    Countly.userData.set(key, value) //set custom property
    +
    Countly.userData.set(key, value) //set custom property
     Countly.userData.set_once(key, value) //set custom property only if property does not exist
     Countly.userData.increment(key) //increment value in key by one
     Countly.userData.increment_by(key, value) //increment value in key by provided value
    @@ -511,7 +530,7 @@ Countly.userData.save() //send userData to server
    it depend on which trace you report.

    Here is an example of how to report network trace:

    -
    //report network trace
    +
    //report network trace
     Countly.report_trace({
       type: "network", //device or network
       name: "/some/endpoint", //use name to identify trace and group them by
    @@ -525,7 +544,7 @@ Countly.report_trace({
       }
     });

     And here is an example of device trace:

    -
    //user built in method to report app start time
    +
    //user built in method to report app start time
     Countly.report_app_start();
     
     //or report device trace manually
    @@ -539,7 +558,7 @@ Countly.report_trace({
       }
     });

    Or you can report any custom traces to provide duration:

    -
    //or report device trace manually
    +
    //or report device trace manually
     Countly.report_trace({
       type: "device", //device or network
       name: "Some process we launched", //use name to identify trace and group them by
    @@ -555,82 +574,88 @@ Countly.report_trace({
       Here are the properties you can setup on Countly initialization
     

      -
    • +
    • app_key - Mandatory! The App Key for your app which created in Countly.
    • -
    • +
    • url - Mandatory! Countly server URL. You must use your server URL here!
    • -
    • +
    • device_id - To identify a visitor, SDK will auto-generate if not provided!
    • -
    • +
    • app_version - Version of your app or website.
    • -
    • +
    • country_code - Country code for your visitor.
    • -
    • +
    • city - Name of the city of your visitor.
    • -
    • +
    • ip_address - IP address of your visitor.
    • -
    • +
    • debug - Output debug info into the console (default: false).
    • -
    • +
    • interval - Set an interval how often to check if there is any data to report and report it (default: 500 ms).
    • -
    • +
    • fail_timeout - Set time in seconds to wait after a failed connection to the server (default: 60 seconds).
    • -
    • +
    • session_update - How often in seconds should session be extended (default: 60 seconds).
    • -
    • +
    • max_events - Maximum amount of events to send in one batch (default: 10).
    • -
    • +
    • force_post - Force using the post method for all requests (default: false)
    • -
    • +
    • storage_path - Where SDK would store data, including id, queues, etc. (default: "../data/").
    • -
    • - storage_type - Determines which storage type will be applied. - Built-in storage type options are: +
    • +

      + storage_type - Determines which storage type will be + applied. Built-in storage type options are: +

        -
      • +
      • File Storage: Countly.StorageTypes.FILE
      • -
      • +
      • Memory Storage: Countly.StorageTypes.MEMORY
      - If custom_storage_method will be used, do not use the storage type. If the - user didn't set the storage type, SDK applies the default. Default is: - Countly.StorageTypes.FILE +

      + If custom_storage_method will be used, do not use the storage type. If + the user didn't set the storage type, SDK applies the default. Default + is: Countly.StorageTypes.FILE +

    • -
    • - custom_storage_method - User-given storage methods that - can be used instead of the default File Storage or the optional Memory Storage - methods. +
    • +

      + custom_storage_method - User-given storage methods that + can be used instead of the default File Storage or the optional Memory + Storage methods. +

        -
      • +
      • If no storage_path is provided with the custom method, the storage path will be the default path! (default: "../data/")
      • -
      • +
      • The object must contain storeGet, storeSet, and storeRemove functions!
      @@ -642,24 +667,24 @@ Countly.report_trace({

    -
  • +
  • require_consent - pass true if you are implementing GDPR compatible consent management. It would prevent running any functionality without proper consent (default: false).
  • -
  • +
  • remote_config - Enable automatic remote config fetching, provide callback function to be notified when fetching done (default: false).
  • -
  • +
  • http_options - Function to get http options by reference and overwrite them, before running each request.
  • -
  • +
  • max_breadcrumb_count - Maximum amount of breadcrumbs to store for crash logs (default: 100)
  • -
  • +
  • metrics - Provide metrics override or custom metrics for this user. For more information on the specific metric keys used by Countly, check @@ -667,7 +692,7 @@ Countly.report_trace({

Setting up properties in Countly NodeJS SDK is as follows

-
Countly.init({
+
Countly.init({
   debug:false,
   app_key:"YOUR_APP_KEY",
   device_id:"1234-1234-1234-1234",
@@ -712,7 +737,7 @@ Countly.report_trace({
   max_key_length - 128 chars by default. Keys that
   exceed this limit will be truncated.
 

-
Countly.init({
+
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "YOUR_SERVER_URL",
   max_key_length: 50
@@ -722,7 +747,7 @@ Countly.report_trace({
   max_value_size - 256 chars by default. Values
   that exceed this limit will be truncated.
 

-
Countly.init({
+
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "YOUR_SERVER_URL",
   max_value_size: 12
@@ -732,7 +757,7 @@ Countly.report_trace({
   max_segmentation_values - 100 dev entries by
   default. Key/value pairs that exceed this limit in a single event will be removed.
 

-
Countly.init({
+
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "YOUR_SERVER_URL",
   max_segmentation_values: 67
@@ -742,7 +767,7 @@ Countly.report_trace({
   max_breadcrumb_count - 100 entries by default.
   If the limit is exceeded, the oldest entry will be removed from stored breadcrumbs.
 

-
Countly.init({
+
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "YOUR_SERVER_URL",
   max_breadcrumb_count: 45
@@ -753,7 +778,7 @@ Countly.report_trace({
   default. Crash stack trace lines that exceed this limit (per thread) will be
   removed.
 

-
Countly.init({
+
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "YOUR_SERVER_URL",
   max_stack_trace_lines_per_thread: 23
@@ -763,7 +788,7 @@ Countly.report_trace({
   max_stack_trace_line_length - 200 chars by default.
   Crash stack trace lines that exceed this limit will be truncated.
 

-
Countly.init({
+
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "YOUR_SERVER_URL",
   max_stack_trace_line_length: 10
@@ -792,7 +817,7 @@ Countly.report_trace({
   1,000 will be used.
 

-
Countly.init({
+  
Countly.init({
   app_key:"YOUR_APP_KEY",
   url: "https://try.count.ly",
   queueSize: 5000
@@ -807,7 +832,7 @@ Countly.report_trace({
   Note: that conversion for each user may be reported only once, all other conversions
   will be ignored for this same user
 

-
//or provide campaign id yourself
+
//or provide campaign id yourself
 Countly.report_conversion("MyCampaignID");

Make Direct Request

@@ -816,7 +841,7 @@ Countly.report_conversion("MyCampaignID");

with all possible SDK parameters described in API reference

-
Countly.request({
+
Countly.request({
   app_key:"somekey", 
   devide_id:"someid", 
   events:"[{'key':'val','count':1}]", 
@@ -841,11 +866,11 @@ Countly.report_conversion("MyCampaignID");

While evaluating objects, SDK checks for, if:

    -
  • an object is provided
  • -
  • +
  • an object is provided
  • +
  • that object has the valid keys (storeSet, storeGet, storeRemove)
  • -
  • the values are functions
  • +
  • the values are functions

In cases where an invalid object is provided as the custom method, SDK will ignore @@ -870,47 +895,47 @@ Countly.report_conversion("MyCampaignID");

Custom Storage Object must contain the 3 main methods SDK uses internally;

    -
  • +
  • storeSet(key, value, callback) - must save the provided key/value pair into the storage. Callback is a function that is invoked to indicate the result of the operation after the operation is complete or failed. Parameters:
      -
    • +
    • key(string) - the key to associate with the value.
    • -
    • +
    • value(null | boolean | number | string | object) - the value to store (can be null, primitive, or an object).
    • -
    • +
    • callback(function) - after the value is stored under the given key, you should call the callback if given, with the error object of the failed operation or with null if the operation succeeds.
  • -
  • +
  • storeGet(key, def) - must return the value for the provided key. If the value for the key does not exist, it should return the def value. Parameters:
      -
    • +
    • key(string) - the key that's associated with the value.
    • -
    • +
    • def(null | boolean | number | string | object) - default value to use if it doesn't set (can be null, primitive, or an object).
  • -
  • +
  • storeRemove(key) - must remove the key and value from the - storage for the provided key. Parameters:
    + storage for the provided key. Parameters:
      -
    • +
    • key(string) - the key that's associated with the value to remove.
    • @@ -918,7 +943,7 @@ Countly.report_conversion("MyCampaignID");

Custom Storage Method object should be like this:

-
const customStorageMethods = {
+
const customStorageMethods = {
     /**
      * @example
      * customStorageMethods.storeSet('myKey', 'myValue', function(error) {
@@ -953,7 +978,7 @@ Countly.report_conversion("MyCampaignID");
In some cases you may want to change the ID of the user/device that you provided or Countly generated automatically, for example, when user was changed.

-
Countly.change_id("myNewId");
+
Countly.change_id("myNewId");

If device ID is changed without merging and consent was enabled, all previously given consent will be removed. This means that all features will cease to function until new consent has been given again for that new device ID. @@ -970,7 +995,7 @@ Countly.report_conversion("MyCampaignID");

This call will merge any data recorded for current ID and save it as user with new provided ID.

-
Countly.change_id("myNewId", true);
+
Countly.change_id("myNewId", true);

FAQ

What Information is Collected by the SDK?

From 040533b62e11224002a5a0154b246adb8a183e87 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Dec 2025 13:38:19 +0300 Subject: [PATCH 5/8] feat: migrate flutter to new editor --- flutter/current.md | 786 +++++++++++++++++++++++---------------------- flutter/next.md | 782 ++++++++++++++++++++++---------------------- 2 files changed, 789 insertions(+), 779 deletions(-) diff --git a/flutter/current.md b/flutter/current.md index badf4e35..623f86b8 100644 --- a/flutter/current.md +++ b/flutter/current.md @@ -15,61 +15,80 @@ 4.0, tvOS 10.0, macOS 10.14), and it requires Xcode 13.0+.

- For Android builds, this SDK requires a minimum Android version of 4.2.x (API - Level 17). + For Android builds, this SDK requires a minimum Android version of 5.0 (API Level + 21).

For Web builds, this SDK is compatible with browsers that support ECMAScript 5. Minimum versions of major internet browsers that fully support ES5 are:

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- IE - - Edge - - Firefox - - Firefox (Android) - - Opera - - Opera (Mobile) - - Safari - - Safari (iOS) - - Chrome - - Chrome (Android) -
101221961564662398
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ IE + + Edge + + Firefox + + Firefox (Android) + + Opera + +

+ Opera +

+

+ (Mobile) +

+
+ Safari + +

+ Safari +

+

+ (iOS) +

+
+ Chrome + +

+ Chrome +

+

+ (Android) +

+
101221961564662398
+

To examine the example integrations, please have a look here. @@ -78,19 +97,19 @@

Add this to your project's pubspec.yaml file:

-
dependencies:
+
dependencies:
   countly_flutter: ^25.4.0

After you can install packages from the command line with Flutter:

-
flutter pub get
+
flutter pub get

SDK Integration

Minimal Setup

The shortest way to initialize the SDK, if you want Countly SDK to take care of device ID seamlessly, is to use the code below.

-
// Create the configuration with your app key and server URL
+
// Create the configuration with your app key and server URL
 CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // Initialize with that configuration
@@ -123,16 +142,16 @@ Countly.initWithConfig(config).then((value){
 

SDK Data Storage

SDK data storage locations are platform-specific:

    -
  • +
  • For iOS, the SDK data is stored in the Application Support Directory in a file named "Countly.dat"
  • -
  • +
  • For Android, the SDK data is stored in SharedPreferences. A SharedPreferences object points to a file containing key-value pairs and provides simple reading and writing methods.
  • -
  • +
  • For Web, the SDK data is stored in Local Storage. It can be modified to use memory only or cookies.
  • @@ -145,7 +164,7 @@ Countly.initWithConfig(config).then((value){

    We advise doing this while implementing Countly features in your application.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setLoggingEnabled(true);

    For more information on where to find the SDK logs you can check the documentation @@ -167,7 +186,7 @@ config.setLoggingEnabled(true);

    If you want to enable automatic unhandled crash reporting, you need to call this before init:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.enableCrashReporting()

    By doing that it will automatically catch all errors that are thrown from within @@ -184,7 +203,7 @@ config.enableCrashReporting()

    The following call will add the provided segmentation to all recorded crashes. Use the following function for this purpose:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setCustomCrashSegment(Map<String, Object> segments);

    Handled Exceptions

    @@ -195,17 +214,17 @@ config.setCustomCrashSegment(Map<String, Object> segments);

    be provided to the function. A potential use case would be to exception.toString()

    -
    Countly.logException(String exception, bool nonfatal, [Map<String, Object> segmentation])
    +
    Countly.logException(String exception, bool nonfatal, [Map<String, Object> segmentation])

    The issue is recorded with a provided Exception object. If no stacktrace is set,StackTrace.current will be used.

    -
    Countly.logExceptionEx(Exception exception, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})
    +
    Countly.logExceptionEx(Exception exception, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})

    The exception/error is recorded through a string message. If no stack trace is provided, StackTrace.current will be used.

    -
    Countly.logExceptionManual(String message, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})
    +
    Countly.logExceptionManual(String message, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})

    Below are some examples that how to log handled/nonfatal and unhandled/fatal exceptions manually. @@ -213,7 +232,7 @@ config.setCustomCrashSegment(Map<String, Object> segments);

1. Manually report exception

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal);
 
@@ -223,7 +242,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal);
 

2. Manually report exception with stack trace

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, stacktrace: STACK_TRACE_OBJECT);
 
@@ -233,7 +252,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, stacktrace: STACK_TRACE_O
 

3. Manually report exception with segmentation

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, segmentation: {"_facebook_version": "0.0.1"});
 
@@ -243,7 +262,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, segmentation: {"_facebook
 

4. Manually report exception with stack trace and segmentation

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, STACK_TRACE_OBJECT, {"_facebook_version": "0.0.1"});
 
@@ -257,7 +276,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_fa
   will be sent together with the crash report.
 

The following function call adds a crash breadcrumb:

-
Countly.addCrashLog(String logs)
+
Countly.addCrashLog(String logs)

Events

Event is any type of action that @@ -272,7 +291,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_fa In the SDK all event-related functionality can be browsed from the returned interface on:

-
Countly.instance.events
+
Countly.instance.events

When providing segmentation for events, the following primitive data types are supported: "String," "int," "double," and "bool." Additionally, Lists composed @@ -285,43 +304,43 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_fa of what information each usage will provide us:

    -
  • +
  • Usage 1: how many times a purchase event occurred.
  • -
  • +
  • Usage 2: how many times a purchase event occurred + the total amount of those purchases.
  • -
  • +
  • Usage 3: how many times a purchase event occurred + which countries and application versions those purchases were made from.
  • -
  • +
  • Usage 4: how many times a purchase event occurred + the total amount both of which are also available segmented into countries and application versions.
  • -
  • +
  • Usage 5: how many times purchase event occurred + the total amount both of which are also available segmented into countries and application versions + the total duration of those events (under Timed Events topic below).

The function signature as follows

-
Future<String?> recordEvent(String key, [Map<String, Object>? segmentation, int? count, double? sum, int? duration])
+
Future<String?> recordEvent(String key, [Map<String, Object>? segmentation, int? count, double? sum, int? duration])

1. Event key and count

-
Countly.instance.events.recordEvent('purchase', null, 1);
+
Countly.instance.events.recordEvent('purchase', null, 1);

2. Event key, count and sum

-
Countly.instance.events.recordEvent('purchase', null, 1, 0.99);
+
Countly.instance.events.recordEvent('purchase', null, 1, 0.99);
 

3. Event key and count with segmentation(s)

-
Map<String, Object>? segmentation = {
+
Map<String, Object>? segmentation = {
   'country': 'Germany',
   'app_version': '1.0',
   'rating': 10,
@@ -337,7 +356,7 @@ Countly.instance.events.recordEvent('purchase', segmentation, 1);
 

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

-
Map<String, Object>? segmentation = {
+
Map<String, Object>? segmentation = {
   'country': 'Germany',
   'app_version': '1.0',
   'rating': 10,
@@ -353,7 +372,7 @@ Countly.instance.events.recordEvent('purchase', segmentation, 1, 0.99);
 

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

-
Map<String, Object>? segmentation = {
+
Map<String, Object>? segmentation = {
   'country': 'Germany',
   'app_version': '1.0',
   'rating': 10,
@@ -370,7 +389,7 @@ Countly.instance.events.recordEvent('purchase', segmentation, 1, 0.99, 1);
 

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

-
// Basic event
+
// Basic event
 Countly.instance.events.startEvent("Timed Event");
 
 Timer timer = Timer(new Duration(seconds: 5), () {
@@ -378,9 +397,11 @@ Timer timer = Timer(new Duration(seconds: 5), () {
 });
 

- You may also provide additional information when ending an event. However, in that case, you have to provide the segmentation, count, and sum. The default values for those are "null", 1 and 0. + You may also provide additional information when ending an event. However, in + that case, you have to provide the segmentation, count, and sum. The default + values for those are "null", 1 and 0.

-
// Event with Segment, sum and count
+
// Event with Segment, sum and count
 Countly.instance.events.startEvent("Timed Event With Segment, Sum and Count");
 
 Timer timer = Timer(new Duration(seconds: 5), () {
@@ -400,7 +421,7 @@ Timer timer = Timer(new Duration(seconds: 5), () {
 

You may cancel the started timed event in case it is not relevant anymore:

-
//start some event
+
//start some event
 Countly.instace.events.startEvent(eventName);
 
 //wait some time
@@ -418,16 +439,16 @@ Countly.instance.events.cancelEvent(eventName);
   track a session automatically. This is how it works:
 

    -
  • +
  • Start/Begin session Request: It is sent to the server when the app comes back to the foreground from the background, and it includes basic metrics.
  • -
  • +
  • Update Session Request: It automatically sends a periodical (60 sec by default) update session request while the app is in the foreground.
  • -
  • +
  • End Session Request: It is sent at the end of a session when the app goes to the background or terminates.
  • @@ -444,22 +465,23 @@ Countly.instance.events.cancelEvent(eventName); relying on the SDK.

    It can be enabled during init with:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    -config.enableManualSessionHandling();
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +

    Afterwards it is up to the implementer to make calls to:

      -
    • Begin session (Starts a session)
    • -
    • +
    • Begin session (Starts a session)
    • +
    • Update session duration (By default, you would call this every 60 seconds after beginning a session so that it is not closed server side. If you would - want to increase that duration, you would have to increase the "Maximal Session Duration" in your server API configuration) + want to increase that duration, you would have to increase the "Maximal Session + Duration" in your server API configuration)
    • -
    • End session (Ends and updates duration)
    • +
    • End session (Ends and updates duration)

    You can use the 'sessions interface' to make these calls:

    -
    Countly.instance.sessions.beginSession();
    +
    Countly.instance.sessions.beginSession();
     Countly.instance.sessions.updateSession();
    -Countly.instance.sessions.endSession();
    +Countly.instance.sessions.endSession();

    View Tracking

    @@ -471,7 +493,7 @@ Countly.instance.sessions.endSession();

The SDK provides access to all view-related functionality through the interface returned by:

-
Countly.instance.views
+
Countly.instance.views

Manual View Recording

You can manually track views in your application.

@@ -488,12 +510,12 @@ Countly.instance.sessions.endSession();

If you want to start a view that will be automatically stopped when starting another view, use the following method:

-
// record a view on your application
-final String? viewID = await Countly.instance.views.startAutoStoppedView("Dashboard");
+
// record a view on your application
+final String? viewID = await Countly.instance.views.startAutoStoppedView("Dashboard");

You can also specify the custom segmentation key-value pairs while starting views:

-
Map<String, Object> segmentation = {
+
Map<String, Object> segmentation = {
   "country": "Germany",
   "app_version": "1.0",
   "rating": 10,
@@ -504,21 +526,21 @@ final String? viewID = await Countly.instance.views.startAutoStoppedViewstartAutoStoppedView("HomePage", segmentation);
+final String? anotherViewID = Countly.instance.views.startAutoStoppedView("HomePage", segmentation);
 

Regular Views

Opposed to "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 manually start a view using the startViewmethod with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName or stopViewWithID + can manually start a view using the startViewmethod with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName or stopViewWithID

-
// record a view on your application
+
// record a view on your application
 Countly.instance.views.startView("HomePage");
 final String? viewID = await Countly.instance.views.startView("Dashboard");

You can also specify the custom segmentation key-value pairs while starting views:

-
Map<String, Object> segmentation = {
+
Map<String, Object> segmentation = {
   "country": "Germany",
   "app_version": "1.0",
   "rating": 10,
@@ -537,30 +559,30 @@ final String? anotherViewID = Countly.instance.views.startView("HomePage", segme
   you try to stop one with that name, the SDK would close one of those randomly.
 

Below you can see example ways of stopping views.

-
Countly.instance.views.stopViewWithName("HomePage");
+
Countly.instance.views.stopViewWithName("HomePage");

This function allows you to manually stop the tracking of a view identified by its name. You can also specify the custom segmentation key-value pairs while stopping views:

-
Countly.instance.views.stopViewWithName("HomePage", segmentation);
+
Countly.instance.views.stopViewWithName("HomePage", segmentation);

You can also stop view tracking by its unique idetifier using - stopViewWithID + stopViewWithID

-
Countly.instance.views.stopViewWithID(viewID);
+
Countly.instance.views.stopViewWithID(viewID);

You can also specify the custom segmentation key-value pairs while stopping views:

-
Countly.instance.views.stopViewWithID(anotherViewID, segmentation);
+
Countly.instance.views.stopViewWithID(anotherViewID, segmentation);

You can stop all views tracking using - stopAllViews + stopAllViews

-
Countly.instance.views.stopAllViews();
+
Countly.instance.views.stopAllViews();

You can also specify the custom segmentation key-value pairs while stopping all views:

-
Countly.instance.views.stopAllViews(segmentation);
+
Countly.instance.views.stopAllViews(segmentation);

Pausing and Resuming Views

This SDK allows you to start multiple @@ -571,18 +593,19 @@ final String? anotherViewID = Countly.instance.views.startView("HomePage", segme

You can pause view tracking by its unique identifier using - pauseViewWithID + pauseViewWithID

-
Countly.instance.views.pauseViewWithID(viewID);
+
Countly.instance.views.pauseViewWithID(viewID);

- This function temporarily pauses the tracking of a view identified by its unique identifier. + This function temporarily pauses the tracking of a view identified by its unique + identifier.

- You can resume view tracking by its unique identifier using resumeViewWithID: + You can resume view tracking by its unique identifier using resumeViewWithID:

-
Countly.instance.views.resumeViewWithID(viewID);
+
Countly.instance.views.resumeViewWithID(viewID);

- This function resumes the tracking of a view identified by its unique identifier. + This function resumes the tracking of a view identified by its unique identifier.

Adding Segmentation to Started Views

@@ -599,7 +622,7 @@ final String? anotherViewID = Countly.instance.views.startView("HomePage", segme

Here is an example on how to achieve that using the view name:

-
String viewName = 'HomePage';
+
String viewName = 'HomePage';
 await Countly.instance.views.startView(viewName);
 
 Map<String, Object> segmentation = {
@@ -617,7 +640,7 @@ await Countly.instance.views.addSegmentationToViewWithName(viewName, segmentatio
 

Here is an example for how to add segmentation to a view using its ID:

-
String? viewID = await Countly.instance.views.startView('HomePage');
+
String? viewID = await Countly.instance.views.startView('HomePage');
 
 Map<String, Object> segmentation = {
   "country": "Germany",
@@ -642,7 +665,7 @@ await Countly.instance.views.addSegmentationToViewWithID(viewID!, segmentation);
   For setting global segmentation values during SDK initialization, use the following
   method:
 

-
// set global segmentation at initialization
+
// set global segmentation at initialization
 final CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setGlobalViewSegmentation(segmentation);

@@ -653,12 +676,12 @@ config.setGlobalViewSegmentation(segmentation);

ThesetGlobalViewSegmentation method will replace the previously set values..

-
Countly.instance.views.setGlobalViewSegmentation(segmentation);
+
Countly.instance.views.setGlobalViewSegmentation(segmentation);

The updateGlobalViewSegmentation method will modify the previously set values and overwrite any previously set keys.

-
Countly.instance.views.updateGlobalViewSegmentation(segmentation);
+
Countly.instance.views.updateGlobalViewSegmentation(segmentation);

Device ID Management

A device ID is a unique identifier for your users. You may specify the device @@ -669,7 +692,7 @@ config.setGlobalViewSegmentation(segmentation);

You may provide your custom device ID when initializing the SDK:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setDeviceId(DEVICE_ID);

Changing the Device ID

@@ -680,7 +703,7 @@ config.setDeviceId(DEVICE_ID);

You may configure or change the device ID anytime using:

-
Countly.instance.deviceId.setID(DEVICE_ID);
+
Countly.instance.deviceId.setID(DEVICE_ID);

When using setID, the SDK determines internally if the device will be counted as a new device on the server or if it will merge the new and old @@ -706,7 +729,7 @@ config.setDeviceId(DEVICE_ID);

You can enable temporary device ID mode when initializing the SDK:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.enableTemporaryDeviceIDMode();
 
 // Initialize with that configuration
@@ -714,7 +737,7 @@ Countly.initWithConfig(config);

To enable a temporary device ID after initialization, you can call:

-
Countly.instance.deviceId.enableTemporaryIDMode();
+
Countly.instance.deviceId.enableTemporaryIDMode();

The SDK will be in temporary device ID mode, all requests will be on hold and they will be persistently stored. @@ -733,23 +756,20 @@ Countly.initWithConfig(config);

You may want to see what the current device ID is. For that, you can use the following call:

-
String? currentDeviceId = Countly.instance.deviceId.getID();
+
String? currentDeviceId = Countly.instance.deviceId.getID();

- You can use getIDType method which returns a - DeviceIDType to get the current device ID type. The ID type is an enum with the possible values of: + You can use getIDType method which returns a + DeviceIDType to get the current device ID type. The ID type is an + enum with the possible values of:

    -
  • - "DEVELOPER_SUPPLIED" - device ID was supplied by the host app. -
  • -
  • - "SDK_GENERATED" - device ID was generated by the SDK. -
  • -
  • - "TEMPORARY_ID" - the SDK is in temporary device ID mode. +
  • + "DEVELOPER_SUPPLIED" - device ID was supplied by the host app.
  • +
  • "SDK_GENERATED" - device ID was generated by the SDK.
  • +
  • "TEMPORARY_ID" - the SDK is in temporary device ID mode.
-
DeviceIdType? deviceIdType = await Countly.instance.deviceId.getIDType();
+
DeviceIdType? deviceIdType = await Countly.instance.deviceId.getIDType();

Device ID Generation

When the SDK is initialized for the first time with no device ID, it will generate @@ -792,7 +812,7 @@ Countly.initWithConfig(config);

android/app/src/main/AndroidManifest.xml inside application tag.

-
<application ...>
+
<application ...>
 ...
   <service android:name="ly.count.dart.countly_flutter.CountlyMessagingService">
     <intent-filter>
@@ -804,7 +824,7 @@ Countly.initWithConfig(config);

Step 6: Add the following line in file android/build.gradle

-
buildscript {
+
buildscript {
   dependencies {
     classpath 'com.google.gms:google-services:LATEST'
     }
@@ -819,7 +839,7 @@ Countly.initWithConfig(config);

Step 7: Add the following line in file android/app/build.gradle

-
dependencies {
+
dependencies {
   implementation 'ly.count.android:sdk:LATEST'
   implementation 'com.google.firebase:firebase-messaging:LATEST'
 }
@@ -851,15 +871,15 @@ apply plugin: 'com.google.gms.google-services'
   For this purpose you can find CountlyNotificationService.h/m file
   under:
 

-
Pods/Development Pods/Countly/{PROJECT_NAME}/ios/.symlinks/plugins/countly_flutter/ios/Classes/CountlyiOS/CountlyNotificationService.h/m
+
Pods/Development Pods/Countly/{PROJECT_NAME}/ios/.symlinks/plugins/countly_flutter/ios/Classes/CountlyiOS/CountlyNotificationService.h/m

Some tips to find the files from deep hierarchy:

    -
  • +
  • You can filter the files in the navigator using a shortcut ⌥⌘J (Option-Command-J), in the filter box type "CountlyNotificationService" and it will show the related files only.
  • -
  • +
  • You can find the file using the shortcut ⇧⌘O (Shift-Command-O) and then navigate to that file using the shortcut ⇧⌘J (Shift-Command-J)
  • @@ -874,18 +894,18 @@ apply plugin: 'com.google.gms.google-services' token mode. This would allow you to choose either test or production modes, push token mode should be set before init.

    -
    // Set messaging mode for push notifications
    +
    // Set messaging mode for push notifications
     Countly.pushTokenType(Countly.messagingMode["TEST"]);

    When you are finally ready to initialise Countly push, you would call this:

    -
    // This method will ask for permission, enables push notification and send push token to countly server.
    +
    // This method will ask for permission, enables push notification and send push token to countly server.
     Countly.askForNotificationPermission();

    Also it is important to note that push notification is enabled for iOS by default, so to disable you need to call disablePushNotifications method:

    -
    // Disable push notifications feature for iOS, by default it is enabled.
    +
    // Disable push notifications feature for iOS, by default it is enabled.
     Countly.disablePushNotifications();

    Removing Push and Its Dependencies

    @@ -898,18 +918,18 @@ Countly.disablePushNotifications();

    To register a Push Notification callback after initializing the SDK, use the method below.

    -
    Countly.onNotification((String notification) {
    +
    Countly.onNotification((String notification) {
       print(notification);
    -});
    +});

    In order to listen to notification receive and click events, Place below code in AppDelegate.swift

    Add header files

    -
    import countly_flutter
    +
    import countly_flutter
     

    Add these methods:

    -
    // Required for the notification event. You must call the completion handler after handling the remote notification.
    +
    // Required for the notification event. You must call the completion handler after handling the remote notification.
     func application(application: UIApplication,  didReceiveRemoteNotification userInfo: [NSObject : AnyObject],  fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
       CountlyFlutterPlugin.onNotification(userInfo);
       completionHandler(.newData);
    @@ -938,7 +958,7 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec
       Here is the example of how data will receive in push callbacks:Screenshot_2022-06-24_at_7.04.23_PM.png
       Data Received for Android platform:
     

    -
    {
    +
    {
       "c.e.cc": "TR",
       "c.e.dt": "mobile",
       "Key": "value",
    @@ -950,9 +970,9 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec
       "sound": "custom",
       "title": "title",
       "message": "Message"
    -}
    +}

    Data Received for iOS platform:

    -
    {
    +
    {
       Key = value;
       aps = {
         alert = {
    @@ -973,7 +993,7 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec
         i = 62b5b945cabedb0870e9f217;
         l = "https://www.google.com/";
       };
    -}
    +}

    User Location

    @@ -996,23 +1016,21 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec When setting user location information, you would be setting these values:

      -
    • +
    • countryCode a string in ISO 3166-1 alpha-2 format country code
    • -
    • +
    • city a string specifying city name
    • -
    • +
    • location a string comma-separated latitude and longitude
    • -
    • +
    • IP a string specifying an IP address in IPv4 or IPv6 formats
    -

    - All values are optional, but at least one should be set. -

    -
    // Example for setLocation
    +

    All values are optional, but at least one should be set.

    +
    // Example for setLocation
     CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setLocation(country_code: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12')

    @@ -1020,7 +1038,7 @@ config.setLocation(country_code: 'TR', city: 'Istanbul', gpsCoordinates: '41.008 SDK has started. To do so, use the setUserLocation method as shown below.

    -
    // Example for setUserLocation
    +
    // Example for setUserLocation
     Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12');
     

    Disable Location

    @@ -1030,7 +1048,7 @@ Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41 setUserLocation is called with any non-null value, tracking will resume.

    -
    //disable location tracking
    +
    //disable location tracking
     Countly.disableLocation();

    Remote Config

    @@ -1050,9 +1068,7 @@ Countly.disableLocation();

    stay as they were. A previously valid key may return no value after a full download.

    -

    - Downloading Values -

    +

    Downloading Values

    Automatic Remote Config Triggers

    @@ -1067,17 +1083,17 @@ Countly.disableLocation();

    The automatic download triggers that would trigger a full value download are:

      -
    • +
    • when the SDK has finished initializing
    • -
    • +
    • after the device ID is changed without merging
    • -
    • +
    • when user gets out of temp ID mode
    • -
    • - when CountlyConsent.remoteConfig consent is given after it had been removed before (if consents are enabled) +
    • + when CountlyConsent.remoteConfig consent is given after it had been removed before (if consents are enabled)

    @@ -1085,7 +1101,7 @@ Countly.disableLocation();

    enableRemoteConfigAutomaticTriggers on the configuration object you will provide during init.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    +  
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
       ..enableRemoteConfigAutomaticTriggers(); // necessary to enable the feature
     

    @@ -1094,33 +1110,33 @@ Countly.disableLocation();

    were not updated, you would have metadata indicating if a value belongs to the old or current user.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    -  ..enableRemoteConfigValueCaching(); 
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    +  ..enableRemoteConfigValueCaching(); 

    Manually 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, omitting (everything except) keys.

    Each of these calls also has an optional parameter that you can provide a RCDownloadCallback to, which would be triggered when the download attempt has finished.

    - dowloadAllKeys is the same as the automatically triggered 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 automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones).

    - Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array. + Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array.

    - Or you might want to update all the values except a few defined keys. To do so,  call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array. + Or you might want to update all the values except a few defined keys. To do so,  call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array.

    @@ -1129,7 +1145,7 @@ Countly.disableLocation();
    Omit Keys
-
Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) {
+    
Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) {
   if (rResult == RequestResult.Success) {
     // do sth
   } else {
@@ -1138,7 +1154,7 @@ Countly.disableLocation();
});

- When making requests with an "inclusion" or "exclusion" 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 "inclusion" or "exclusion" 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

@@ -1165,10 +1181,10 @@ Countly.disableLocation(); key. This returns an Future<RCData> object that contains the value of the key and the metadata about that value's owner. If value in RCData was null - then no value was found or the value was null. + then no value was found or the value was null.  

-
Object? value_1 = await Countly.instance.remoteConfig.getValue("key_1").value;
+
Object? value_1 = await Countly.instance.remoteConfig.getValue("key_1").value;
 Object? value_2 = await Countly.instance.remoteConfig.getValue("key_2").value;
 Object? value_3 = await Countly.instance.remoteConfig.getValue("key_3").value;
 Object? value_4 = await Countly.instance.remoteConfig.getValue("key_4").value;
@@ -1182,10 +1198,10 @@ JSONObject jObj = value4 as JSONObject;
   If you want to get all values together you can use
   getAllValues which returns a Future<Map<String,
   RCData>>.
-  The SDK does not know the returned value type, so, it will return the Object. The developer then needs to cast it to the appropriate type. The returned values may also be JSONArrayJSONObject,
+  The SDK does not know the returned value type, so, it will return the Object. The developer then needs to cast it to the appropriate type. The returned values may also be JSONArrayJSONObject,
   or just a simple value, such as int.
 

-
Map<String, RCData> allValues = await Countly.instance.remoteConfig.getAllValues();
+
Map<String, RCData> allValues = await Countly.instance.remoteConfig.getAllValues();
 
 int intValue = allValues["key_1"] as int;
 double doubleValue = allValues["key_2"] as double;
@@ -1197,7 +1213,7 @@ JSONObject jObj = allValues["key_4"] as JSONObject;
belongs to. The isCurrentUsersData is only false when there was a device ID change, but somehow (or intentionally) a remote config value was not updated.

-
Class RCData {
+
Class RCData {
   Object value;
   Boolean isCurrentUsersData;
 }
@@ -1211,14 +1227,14 @@ JSONObject jObj = allValues["key_4"] as JSONObject;

At some point, you might like to erase all the values downloaded from the server. You will need to call one function to do so.

-
Countly.instance.remoteConfig.clearAll();
+
Countly.instance.remoteConfig.clearAll();

Global Download Callbacks

Also, you may provide a global callback function to be informed when the remote config download request is finished with remoteConfigRegisterGlobalCallback during the SDK initialization:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
   ..remoteConfigRegisterGlobalCallback((rResult, error, fullValueUpdate, downloadedValues) {
     if (error != null) {
       // do sth
@@ -1230,24 +1246,24 @@ JSONObject jObj = allValues["key_4"] as JSONObject;
and it would have the following parameters:

    -
  • +
  • rResult: RequestResult Enum (either - Error, Success or NetworkIssue) + Error, Success or NetworkIssue)
  • -
  • +
  • error: String (error message. "null" if there is no error)
  • -
  • +
  • fullValueUpdate: boolean ("true" - all values updated, "false" - a subset of values updated)
  • -
  • +
  • downloadedValues: Map<String, RCData> (the whole downloaded remote config values)
-
RCDownloadCallback {
+
RCDownloadCallback {
   void callback(RequestResult rResult, String error, boolean fullValueUpdate, Map<String, RCData> downloadedValues)
 }
 
@@ -1261,7 +1277,7 @@ JSONObject jObj = allValues["key_4"] as JSONObject;
You can also register (or remove) callbacks to do different things after the SDK initialization. You can register these callbacks multiple times:

-
// register a callback
+
// register a callback
 Countly.instance.remoteConfig.registerDownloadCallback((rResult, error, fullValueUpdate, downloadedValues) {
   // do sth
 });
@@ -1276,36 +1292,34 @@ Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueU
   from some or all existing A/B tests available.
 

-

-  Enrollment on Download -

+

 Enrollment on Download

You can enroll into the A/B tests automatically whenever you download RC values from the server. To do so you have to set the following flag at the config object during initialization:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
-..enrollABOnRCDownload();
-

-  Enrollment on Access -

+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
+..enrollABOnRCDownload();
+

 Enrollment on Access

- You can also enroll to A/B tests while getting RC values from storage. You can use getValueAndEnroll while getting a single value and getAllValuesAndEnroll while getting all values to enroll to the keys that exist. If no value was stored for those keys these functions would not enroll the user. Both of these functions works the same way with their non-enrolling variants, namely; getValue and getAllValues. + You can also enroll to A/B tests while getting RC values from storage. You + can use getValueAndEnroll while getting a single value and + getAllValuesAndEnroll while getting all values to enroll to + the keys that exist. If no value was stored for those keys these functions + would not enroll the user. Both of these functions works the same way with + their non-enrolling variants, namely; getValue and + getAllValues.

-

-  Enrollment on Action -

+

 Enrollment on Action

To enroll a user into the A/B tests for the given keys you use the following method:

-
Countly.instance.remoteConfig.enrollIntoABTestsForKeys(List<String> keys);
+
Countly.instance.remoteConfig.enrollIntoABTestsForKeys(List<String> keys);

Here the keys array is the mandatory parameter for this method to work.

-

- Exiting A/B Tests -

+

Exiting A/B Tests

Platform Info
@@ -1317,7 +1331,7 @@ Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueU If you want to remove users from A/B tests of certain keys you can use the following function:

-
Countly.instance.remoteConfig.exitABTestsForKeys(List<String> keys);
+
Countly.instance.remoteConfig.exitABTestsForKeys(List<String> keys);

Here if no keys are provided it would remove the user from all A/B tests instead.

@@ -1349,12 +1363,12 @@ Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueU dashboard. If the user dismisses star rating dialog without giving a rating, the event will not be recorded.

-
Countly.askForStarRating();
+
Countly.askForStarRating();

The star-rating dialog's title, message, and dismiss button text may be customized through the following functions:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setStarRatingTextTitle("Custom title"); // Only available for Android
 config.setStarRatingTextMessage("Custom message");
 config.setStarRatingTextDismiss("Custom message"); // Only available for Android
@@ -1368,10 +1382,10 @@ config.setStarRatingTextDismiss("Custom message"); // Only available for Android

It is possible to display 3 kinds of feedback widgets: - NPS, - Survey, + NPS, + Survey, and - Rating. + Rating.

For more detailed information about Feedback Widgets, you can refer to @@ -1387,12 +1401,12 @@ config.setStarRatingTextDismiss("Custom message"); // Only available for Android After you have created widgets on your dashboard, you can reach the methods to show them from the feedback interface of your Countly instance:

-
Countly.instance.feedback
+
Countly.instance.feedback

You can display a random active widget for the widget type you want with one of these methods:

-
.presentNPS([String? nameIDorTag, FeedbackCallback? feedbackCallback])
+
.presentNPS([String? nameIDorTag, FeedbackCallback? feedbackCallback])
 .presentRating([String? nameIDorTag, FeedbackCallback? feedbackCallback])
 .presentSurvey([String? nameIDorTag, FeedbackCallback? feedbackCallback])
 
@@ -1420,7 +1434,7 @@ Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback(
 

For more in-depth information on retrieving feedback widgets, understanding object structures, or presenting them yourself, please refer to the following - resource. + resource.

Manual Reporting

@@ -1445,7 +1459,7 @@ Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback( In case you want to use with callback then you can call 'getFeedbackWidgetData' in this way:

-
Countly.getFeedbackWidgetData(chosenWidget, onFinished: (retrievedWidgetData, error) {
+
Countly.getFeedbackWidgetData(chosenWidget, onFinished: (retrievedWidgetData, error) {
   if (error == null) {
   }
 });
@@ -1453,7 +1467,7 @@ Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback( If you want to use it without a callback then you can call 'getFeedbackWidgetData' in this way:

-
List result = await Countly.getFeedbackWidgetData(chosenWidget);
+
List result = await Countly.getFeedbackWidgetData(chosenWidget);
 String? error = result[1];
 if (error == null) {
   Map<String, dynamic> retrievedWidgetData = result[0];
@@ -1468,7 +1482,7 @@ if (error == null) {
   use it, the widgetInformation and the widgetData to report the feedback result
   with the following call:
 

-
//this contains the reported results
+
//this contains the reported results
 Map<String, Object> reportedResult = {};
 
 //
@@ -1523,25 +1537,25 @@ Countly.reportFeedbackWidgetManually(chosenWidget, retrievedWidgetData , reporte
   Using the following call, you can set both the predefined and the custom user
   properties during initialization:
 

-
var userProperties = {
+
var userProperties = {
   "customProperty": "custom Value",
   "username": "USER_NAME",
   "email": "USER_EMAIL"
 };
 CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setUserProperties(userProperties); 
-

Setting User profile values

+

Setting User profile values

The following calls can be used after init.

If you want to set a single property, you can call Countly.instance.userProfile.setProperty(key, value)

-
Countly.instance.userProfile.setProperty("specialProperty", "value");
+
Countly.instance.userProfile.setProperty("specialProperty", "value");

If you want to set multiple properties at the same time, you can use: Countly.instance.userProfile.setUserProperties(userProperties)

-
// example for setting user data
+
// example for setting user data
 Map<String, Object> userProperties= {
   "name": "Nicola Tesla",
   "username": "nicola",
@@ -1574,7 +1588,7 @@ Countly.instance.userProfile.setUserProperties(userProperties);
the same property.

Below is the list of available methods:

-
//increment used value by 1
+
//increment used value by 1
 Countly.instance.userProfile.increment("increment");
 //increment used value by provided value
 Countly.instance.userProfile.incrementBy("incrementBy", 10);
@@ -1606,7 +1620,7 @@ Countly.instance.userProfile.pull("type", "morning");
to start using them you would first need to enable them and give the required consent if it was required:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // this interface exposes the available APM features and their modifications.
 config.apm. 
@@ -1625,9 +1639,9 @@ config.apm.
with the same key.

To start a custom trace, use:

-
Countly.startTrace(traceKey);
+
Countly.startTrace(traceKey);

To end a custom trace, use:

-
String traceKey = "Trace Key";
+
String traceKey = "Trace Key";
 Map<String, int> customMetric = {
   "ABC": 1233,
   "C44C": 1337
@@ -1643,7 +1657,7 @@ Countly.endTrace(traceKey, customMetric);
for your selected approach of making network requests and then call this after your network request is done:

-
Countly.recordNetworkTrace(networkTraceKey, responseCode, requestPayloadSize, responsePayloadSize, startTime, endTime);
+
Countly.recordNetworkTrace(networkTraceKey, responseCode, requestPayloadSize, responsePayloadSize, startTime, endTime);

networkTraceKey is a unique identifier of the API endpoint you are targeting or just the url you are targeting, all params should be stripped. You @@ -1657,8 +1671,8 @@ Countly.endTrace(traceKey, customMetric);

These are:

    -
  • App Start Time
  • -
  • App Background and Foreground time
  • +
  • App Start Time
  • +
  • App Background and Foreground time

Tracking of these metrics are disabled by default and must be explicitly enabled @@ -1668,10 +1682,10 @@ Countly.endTrace(traceKey, customMetric);

For tracking app start time automatically you will need to enable it in SDK init config:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // enable it here separately with 'apm' interface.
-config.apm.enableAppStartTimeTracking();
+config.apm.enableAppStartTimeTracking();

This calculates and records the app launch time for performance monitoring.

@@ -1680,10 +1694,10 @@ config.apm.enableAppStartTimeTracking();
will have to enable the usage of manual triggers together with enableAppStartTimeTracking during init:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // enable it here separately with 'apm' interface.
-config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger();
+config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger();

Now you can call Countly.appLoadingFinished() any time after SDK initialization to record that moment as the end of app launch time. @@ -1696,21 +1710,21 @@ config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger -

CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // generate the timestamp you want (or you can directly pass a ts)
 int ts = DateTime.now().millisecondsSinceEpoch - 500; // 500 ms ago as an example
 
 // this would also work with manual trigger
-config.apm.enableAppStartTimeTracking().setAppStartTimestampOverride(ts);
+config.apm.enableAppStartTimeTracking().setAppStartTimestampOverride(ts);

Lastly if you want to enable the SDK to record the time an app is in foreground or background automatically you would need to enable this option during init:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // enable it here separately with 'apm' interface.
-config.apm.enableForegroundBackgroundTracking();
+config.apm.enableForegroundBackgroundTracking();

User Consent

For compatibility with data protection regulations, such as GDPR, the Countly @@ -1724,26 +1738,26 @@ config.apm.enableForegroundBackgroundTracking();

Currently, available features with consent control are as follows:

    -
  • +
  • sessions - tracking when, how often and how long users use your app.
  • -
  • events - allow sending events to the server.
  • -
  • views - allow tracking which views user visits.
  • -
  • location - allow sending location information.
  • -
  • crashes - allow tracking crashes, exceptions and errors.
  • -
  • +
  • events - allow sending events to the server.
  • +
  • views - allow tracking which views user visits.
  • +
  • location - allow sending location information.
  • +
  • crashes - allow tracking crashes, exceptions and errors.
  • +
  • attribution - allow tracking from which campaign did user come.
  • -
  • +
  • users - allow collecting/providing user information, including custom properties.
  • -
  • push - allow push notifications
  • -
  • starRating - allow sending their rating and feedback
  • -
  • apm - allow application performance monitoring
  • -
  • +
  • push - allow push notifications
  • +
  • starRating - allow sending their rating and feedback
  • +
  • apm - allow application performance monitoring
  • +
  • remoteConfig - allows downloading remote config values from your server
  • -
  • +
  • content - allow to enter content zone to receive contents from the server.
@@ -1752,7 +1766,7 @@ config.apm.enableForegroundBackgroundTracking();
By default the requirement for consent is disabled. To enable it, you have to call setRequiresConsent with true, before initializing Countly.

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setRequiresConsent(true);

By default, no consent is given. That means that if no consent is enabled, Countly @@ -1766,8 +1780,9 @@ config.setRequiresConsent(true);

of consent values. Or, you can use giveAllConsentsfor all consent values.

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
-config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, CountlyConsent.attribution, CountlyConsent.push, CountlyConsent.events, CountlyConsent.views, CountlyConsent.crashes, CountlyConsent.users, CountlyConsent.push, CountlyConsent.starRating, CountlyConsent.apm, CountlyConsent.feedback, CountlyConsent.remoteConfig, CountlyConsent.content])
config.giveAllConsents()
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, CountlyConsent.attribution, CountlyConsent.push, CountlyConsent.events, CountlyConsent.views, CountlyConsent.crashes, CountlyConsent.users, CountlyConsent.push, CountlyConsent.starRating, CountlyConsent.apm, CountlyConsent.feedback, CountlyConsent.remoteConfig, CountlyConsent.content])
+config.giveAllConsents()

The Countly SDK does not persistently store the status of given consents except push notifications. You are expected to handle receiving consent from end-users @@ -1785,7 +1800,7 @@ config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, Coun To reflect these changes in the Countly SDK, you can use the removeConsent or giveConsent methods.

-
//give consent values after init
+
//give consent values after init
 Countly.giveConsent([CountlyConsent.events, CountlyConsent.views, CountlyConsent.starRating, CountlyConsent.crashes]);
 
 //remove consent values after init
@@ -1794,7 +1809,7 @@ Countly.removeConsent([CountlyConsent.events, CountlyConsent.views, CountlyConse
 

You can also either give or remove consent to all possible SDK features:

-
//give consent to all features
+
//give consent to all features
 Countly.giveAllConsent();
 
 //remove consent from all features
@@ -1809,7 +1824,7 @@ Countly.removeAllConsent();
is set, all requests would be checked for the validity of &checksum field before being processed.

-
// sending data with salt
+
// sending data with salt
 CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setParameterTamperingProtectionSalt("salt");

@@ -1827,70 +1842,81 @@ config.setParameterTamperingProtectionSalt("salt");

Here is the list of functionalities "CountlyConfig" provides:

    -
  • - Device Id - A device ID is a unique identifier for your users. You may specify the device ID yourself or allow the SDK to generate it. +
  • + Device Id - A + device ID is a unique identifier for your users. You may specify the device + ID yourself or allow the SDK to generate it.
  • -
  • - Enable Logging - - To enable countly internal debugging logs. +
  • + Enable Logging - + To enable countly internal debugging logs.
  • -
  • - Enable Crash Reporting - +
  • + Enable Crash Reporting - To enable uncaught crash reporting.
  • -
  • - Salt - +
  • + Salt - Set the optional salt to be used for calculating the checksum of requested - data which will be sent with each request. + data which will be sent with each request.
  • -
  • - Event queue threshold - +
  • + Event queue threshold - Set the threshold for event grouping. Event count that is bellow the threshold - will be sent on update ticks. + will be sent on update ticks.
  • -
  • +
  • Update Session Timer - Sets the interval for the automatic session update calls.
  • -
  • - Custom Crash Segment -Set +
  • + Custom Crash Segment -Set custom crash segmentation which will be added to all recorded crashes.
  • -
  • +
  • User consent - Set if consent should be required and give consents.
  • -
  • - Forcing HTTP POST - When set to true, all requests made to the Countly server will be done using HTTP POST. Otherwise, the SDK sends all requests using the HTTP GET method. In some cases, if the data to be sent exceeds the 1800-character limit, the SDK uses the POST method. The default value is false. +
  • + Forcing HTTP POST - When + set to true, all requests made to the Countly server + will be done using HTTP POST. Otherwise, the SDK sends all requests using + the HTTP GET method. In some cases, if the data to be sent exceeds the 1800-character + limit, the SDK uses the POST method. The default value is false.
  • -
  • - Star Rating Text - Set shown title, message and dismiss buttim text for the star rating dialogs. +
  • + Star Rating Text - + Set shown title, message and dismiss buttim text for the star rating dialogs. (no Web platform support)
  • -
  • - Application Performance Monitoring - Enable APM features, which includes the recording of app start time. +
  • + Application Performance Monitoring - + Enable APM features, which includes the recording of app start time. (no Web platform support)
  • -
  • - Set User Location - Set user location manually instead of using Countly server to use GeoIP database to deduce a user's location. +
  • + Set User Location - + Set user location manually instead of using Countly server to use GeoIP database + to deduce a user's location.
  • -
  • - Max Queue Size Limit - Set maximum size for the request queue. +
  • + Max Queue Size Limit - Set + maximum size for the request queue.
  • -
  • - Manual Sessions - To enable manual session handling - (no Web platform support) +
  • + Manual Sessions - + To enable manual session handling (no Web platform support)
  • -
  • - Automatic Remote Config - If +
  • + Automatic Remote Config - If enabled, will automatically download newest remote config values.
  • -
  • - Direct Attribution - +
  • + Direct Attribution - Report direct user attribution (no Web platform support)
  • -
  • - Indirect Attribution - +
  • + Indirect Attribution - Report indirect user attribution (no Web platform support)
@@ -1900,7 +1926,7 @@ config.setParameterTamperingProtectionSalt("salt");
example application. It assumes Flutter is installed in your system:

-
# clone the Countly SDK repository
+
# clone the Countly SDK repository
 git clone https://github.com/Countly/countly-sdk-flutter-bridge.git
 
 # dive into the cloned repo
@@ -1939,7 +1965,7 @@ flutter run
If you do not specify a value for the setMaxRequestQueueSize flag, the default setting of 1,000 will be used.

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setMaxRequestQueueSize(5000);

SDK Internal Limits

@@ -1953,7 +1979,7 @@ config.setMaxRequestQueueSize(5000);

Limits the maximum size of all user set keys (default: 128 chars):

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.sdkInternalLimits.setMaxKeyLength(int MAX_KEY_LENGTH);
 await Countly.initWithConfig(config);

Value Size

@@ -1961,14 +1987,14 @@ await Countly.initWithConfig(config);
Limits the size of all user set string segmentation (or their equivalent) values (default: 256 chars):

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.sdkInternalLimits.setMaxValueSize(int MAX_VALUE_SIZE);
 await Countly.initWithConfig(config);

Segmentation Values

Limits the amount of user set segmentation key-value pairs (default: 100 entries):

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.sdkInternalLimits.setMaxSegmentationValues(int MAX_SEGMENTATION_COUNT);
 await Countly.initWithConfig(config);

Breadcrumb Count

@@ -1976,21 +2002,21 @@ await Countly.initWithConfig(config);
Limits the amount of user set breadcrumbs that can be recorded (default: 100 entries, exceeding this deletes the oldest one):

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.sdkInternalLimits.setMaxBreadcrumbCount(int MAX_BREADCRUMB_COUNT);
 await Countly.initWithConfig(config);

Stack Trace Lines Per Thread

Limits the stack trace lines that would be recorded per thread (default: 30 lines):

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.sdkInternalLimits.setMaxStackTraceLinesPerThread(int MAX_STACK_THREAD);
 await Countly.initWithConfig(config);

Stack Trace Line Length

Limits the characters that are allowed per stack trace line (default: 200 chars):

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.sdkInternalLimits.setMaxStackTraceLineLength(int MAX_STACK_LENGTH);
 await Countly.initWithConfig(config);

Attribution

@@ -2006,11 +2032,9 @@ await Countly.initWithConfig(config);
from specific campaigns. This feature is available for the Enterprise Edition.

- There are 2 forms of attribution: direct Attribution and indirect Attribution. + There are 2 forms of attribution: direct Attribution and indirect Attribution.

-

- Direct Attribution -

+

Direct Attribution

Platform Info
@@ -2024,16 +2048,17 @@ await Countly.initWithConfig(config);

object.

- You can use recordDirectAttribution to set attribution values during initialization. + You can use recordDirectAttribution to set attribution values during + initialization.

-
String campaignData = 'JSON_STRING';
+
String campaignData = 'JSON_STRING';
 CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.recordDirectAttribution('CAMPAIN_TYPE', campaignData);

You can also use recordDirectAttribution function to manually report attribution later:

-
String campaignData = 'JSON_STRING';
+
String campaignData = 'JSON_STRING';
 Countly.recordDirectAttribution('CAMPAIN_TYPE', campaignData);

Currently this feature is limited and accepts data only in a specific format @@ -2041,20 +2066,19 @@ Countly.recordDirectAttribution('CAMPAIN_TYPE', campaignData);

attribution. The data also needs to be formatted in a specific way. Either with the campaign id or with the campaign id and campaign user id.

-
String campaignData = '{cid:"[PROVIDED_CAMPAIGN_ID]", cuid:"[PROVIDED_CAMPAIGN_USER_ID]"}';
+
String campaignData = '{cid:"[PROVIDED_CAMPAIGN_ID]", cuid:"[PROVIDED_CAMPAIGN_USER_ID]"}';
 Countly.recordDirectAttribution('countly', campaignData);
-

- Indirect Attribution -

+

Indirect Attribution

This feature would be used to report things like advertising ID's. For each platform those would be different values. For the most popular keys we have a class with predefined values to use, it is called "AttributionKey".

- You can use recordDirectAttribution to set attribution values during initialization. + You can use recordDirectAttribution to set attribution values during + initialization.

-
Map<String, String> attributionValues = {};
+
Map<String, String> attributionValues = {};
 if(Platform.isIOS){
   attributionValues[AttributionKey.IDFA] = 'IDFA';
 }
@@ -2063,12 +2087,12 @@ else {
 }
 
 CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
-config.recordIndirectAttribution(attributionValues);
+config.recordIndirectAttribution(attributionValues);

You can also use recordIndirectAttribution function to manually report attribution later

-
Map<String, String> attributionValues = {};
+
Map<String, String> attributionValues = {};
 if(Platform.isIOS){
   attributionValues[AttributionKey.IDFA] = 'IDFA';
 }
@@ -2090,20 +2114,14 @@ Countly.recordIndirectAttribution(attributionValues);
can use the same function later in the app's life cycle to disable the override. This function has to be called every time the app starts.

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setHttpPostForced(true); // default is false

Setting Custom Network Request Headers

-
-

- Platform Info
- This feature is not supported in the Web platform. -

-

If you need to include custom network request headers in the requests sent by the SDK, you can easily add them using the following method.

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setCustomNetworkRequestHeaders({'customHeaderKey': 'customHeaderValue'});

This allows you to specify any headers your application requires for enhanced @@ -2122,7 +2140,7 @@ config.setCustomNetworkRequestHeaders({'customHeaderKey': 'customHeaderValue'}); 1. You can replace all requests with a different app key with the current app key:

-
//Replaces all requests with a different app key with the current app key.
+
//Replaces all requests with a different app key with the current app key.
 Countly.replaceAllAppKeysInQueueWithCurrentAppKey();

In the request queue, if there are any requests whose app key is different than @@ -2130,7 +2148,7 @@ Countly.replaceAllAppKeysInQueueWithCurrentAppKey();

app key. 2. You can remove all requests with a different app key in the request queue:

-
//Removes all requests with a different app key in request queue.
+
//Removes all requests with a different app key in request queue.
 Countly.removeDifferentAppKeysFromQueue();

In the request queue, if there are any requests whose app key is different than @@ -2142,7 +2160,7 @@ Countly.removeDifferentAppKeysFromQueue();

when the server is slow or unresponsive. This helps reduce server load and avoid unnecessary retries. It’s enabled by default but can be disabled if needed.

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.disableBackoffMechanism();

For a detailed explanation of how the backoff mechanism works and when it triggers, @@ -2162,7 +2180,7 @@ config.disableBackoffMechanism();

you don't want to get data older than a certain timeframe, you can configure the SDK to drop old requests:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setRequestDropAgeHours(10); // a positive integer indicating hours

By using the setRequestDropAgeHours method while configuring the @@ -2177,31 +2195,31 @@ config.setRequestDropAgeHours(10); // a positive integer indicating hours event count reaches a threshold. By default it is 10. If you would like to change this, call:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setEventQueueSizeToSend(6);

Checking if the SDK has been initialized

In case you would like to check if init has been called, you may use the following function:

-
Countly.isInitialized();
+
Countly.isInitialized();

Server Configuration

Server Configuration is enabled by default. Changes made on SDK Manager SDK Configuration on your server will affect SDK behavior directly.

-p> - In all cases, the configuration may not be applied during the app’s first run. +

+ p In all cases, the configuration may not be applied during the app’s first run. If this is a security sensitive case for the situations, you can provide the server config to the SDK during initialization.

-
config.setSDKBehaviorSettings("json server config")
+
config.setSDKBehaviorSettings("json server config")

If you want to disable automatic config updates from the server, you can prevent the SDK from making server configuration fetch requests. This is useful if you're trying to reduce network traffic or control request counts.

-
config.disableSDKBehaviorSettingsUpdates()
+
config.disableSDKBehaviorSettingsUpdates()

Content Zone

The Content Zone feature enhances user engagement by delivering various types @@ -2212,7 +2230,7 @@ p>

To start fetching content from the server, use the following method:

-
Countly.instance.content.enterContentZone()
+
Countly.instance.content.enterContentZone()

This call will retrieve and display any available content for the user. It will also regularly check if a new content is available, and if it is, will fetch @@ -2222,16 +2240,16 @@ p> This regular check happens in every 30 seconds by default. It could be configurable while initializing the SDK through and it must be greater than 15 seconds.

-
countlyConfig.content.setZoneTimerInterval(60) //in seconds
+
countlyConfig.content.setZoneTimerInterval(60) //in seconds

If you need to ask for content after a trigger you know you can use this method:

-
Countly.instance.content.refreshContentZone()
+
Countly.instance.content.refreshContentZone()

When you want to exit from content zone and stop SDK from checking for available content you can use this method:

-
Countly.instance.content.exitContentZone()
+
Countly.instance.content.exitContentZone()

To get informed when a user closes a content you can register a global content callback during SDK initialization: @@ -2242,8 +2260,8 @@ p> This feature is not supported in the Web platform.

-
countlyConfig.content.setGlobalContentCallback((contentStatus, contentData))
-
typedef ContentCallback = void Function(ContentStatus contentStatus, Map<String, dynamic> contentData);
+
countlyConfig.content.setGlobalContentCallback((contentStatus, contentData))
+
typedef ContentCallback = void Function(ContentStatus contentStatus, Map<String, dynamic> contentData);

The `contentStatus` will indicate either `ContentStatus.completed` or `ContentStatus.closed`.

@@ -2259,43 +2277,39 @@ p> for enabling advanced features like view name recording and visibility tracking. These features are currently in a testing phase and might change in future versions.

-
CountlyConfig config = CountlyConfig(COUNTLY_APP_KEY, COUNTLY_SERVER_URL);
+
CountlyConfig config = CountlyConfig(COUNTLY_APP_KEY, COUNTLY_SERVER_URL);
 config.experimental.enablePreviousNameRecording().enableVisibilityTracking();

This class allows enabling two experimental features:

    -
  • Previous Name Recording
  • -
  • Visibility Tracking
  • +
  • Previous Name Recording
  • +
  • Visibility Tracking

When you enable previous name recording, it will add previous view name to the view segmentations (cly_pvn) and previous event name to the event segmentations (cly_pen).

-
config.experimental.enablePreviousNameRecording()
+
config.experimental.enablePreviousNameRecording()

When you enable visibility tracking, it will add a parameter (cly_v) to each recorded event's segmentation about the visibility of the app at the time of its recording.

-
config.experimental.enableVisibilityTracking()
+
config.experimental.enableVisibilityTracking()

A/B Experiment Testing

-

- Variant Level Control -

+

Variant Level Control

Platform Info
This feature is not supported in the Web platform.

-

- Downloading -

+

Downloading

You can fetch a map of all A/B testing parameters (keys) and variants associated with it:

-
Countly.instance.remoteConfig.testingDownloadVariantInformation((rResult, error){
+
Countly.instance.remoteConfig.testingDownloadVariantInformation((rResult, error){
   // do sth
 })

@@ -2304,91 +2318,81 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking(); -

- Accessing -

+

Accessing

When test variants are fetched, they are saved to the memory. If the memory is erased, you must fetch the variants again. So a common flow is to use the fetched values right after fetching them. To access all fetched values, you can use:

-
Countly.sharedInstance().remoteConfig().testingGetAllVariants()
+
Countly.sharedInstance().remoteConfig().testingGetAllVariants()

This would return a Future<Map<String, List<String>>> where a test's parameter is associated with all variants under that parameter. The parameter would be the key, and its value would be a String List of variants. For example:

-
{
+
{
   "key_1" : ["variant_1", "variant_2"],
   "key_2" : ["variant_3"]
 }
 

Or instead you can get the variants of a specific key:

-
Countly.sharedInstance().remoteConfig().testingGetVariantsForKey(String valueKey)
+
Countly.sharedInstance().remoteConfig().testingGetVariantsForKey(String valueKey)

This would only return a Future<List<String>> of variants for that specific key. If no variants were present for a key, it would return an empty list. A typical result would look like this:

-
["variant_1", "variant_2"]
+
["variant_1", "variant_2"]
 
-

- Enrolling / Exiting -

+

Enrolling / Exiting

After fetching A/B testing parameters and variants from your server, you next would like to enroll the user to a specific variant. To do this, you can use the following method:

-
Countly.instance.remoteConfig.testingEnrollIntoVariant(String keyName, String variantName, void Function(RequestResult, String?)? callback)
+
Countly.instance.remoteConfig.testingEnrollIntoVariant(String keyName, String variantName, void Function(RequestResult, String?)? callback)

Here the 'valueKey' would be the parameter of your A/B test, and 'variantName' is the variant you have fetched and selected to enroll for. The callback function is optional and works the same way as explained above in the Fetching Test Variants section.

-

- Experiment Level Control -

+

Experiment Level Control

Platform Info
This feature is not supported in the Web platform.

-

- Downloading -

+

Downloading

You can fetch information about the A/B tests in your server including test name, description and the current variant:

-
Countly.instance.remoteConfig.testingDownloadExperimentInformation((rResult, error){
+
Countly.instance.remoteConfig.testingDownloadExperimentInformation((rResult, error){
   // do sth
-})
+})

You can provide a callback (which is optional) to be called when the fetching process ends. Depending on the situation, this would return a RequestResponse Enum (Success, NetworkIssue, or Error) as the first parameter and a String error as the second parameter if there was an error ("null" otherwise).

-

- Accessing -

+

Accessing

After fetching the experiment information the SDK saves it in the RAM, so if the memory is erased, you must fetch the information again. You can access this information through this call:

-
Countly.sharedInstance().remoteConfig().testingGetAllExperimentInfo()
+
Countly.sharedInstance().remoteConfig().testingGetAllExperimentInfo()

This would return a Future<Map<String, ExperimentInformation>> where the keys are experiment IDs as String and the values are the ExperimentInformation Class which contains information about the experiment with that ID. This Class' structure is like this:

-
class ExperimentInformation {
+
class ExperimentInformation {
   // same ID as used in the map
   String experimentID;
   // the name of the experiment
@@ -2404,7 +2408,7 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking();
-
{
+
{
   some_exp_ID: {
     experimentID: some_ID,
     experimentName: some_name,
@@ -2423,19 +2427,17 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking();
-

- Enrolling / Exiting -

+

Enrolling / Exiting

To enroll a user into the A/B experiment using experiment ID, you use the following method:

-
Countly.instance.remoteConfig.testingEnrollIntoABExperiment(String expID);
+
Countly.instance.remoteConfig.testingEnrollIntoABExperiment(String expID);

If you want to remove users from A/B experiment using experiment ID, you can use the following function:

-
Countly.instance.remoteConfig.testingExitABExperiment(String expID);
+
Countly.instance.remoteConfig.testingExitABExperiment(String expID);

Extended Device ID Management

@@ -2445,7 +2447,7 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking();

You may configure/change the device ID anytime using:

-
Countly.changeDeviceId(DEVICE_ID, ON_SERVER);
+
Countly.changeDeviceId(DEVICE_ID, ON_SERVER);

You may either allow the device to be counted as a new device or merge existing data on the server. If theonServer bool is set to @@ -2462,13 +2464,13 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking(); You can enable temporary device ID when initializing the SDK:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setDeviceId(Countly.deviceIDType["TemporaryDeviceID"]);
 
 // Initialize with that configuration
 Countly.initWithConfig(config);

To enable a temporary device ID after init, you would call:

-
Countly.changeDeviceId(Countly.deviceIDType["TemporaryDeviceID"], ON_SERVER);
+
Countly.changeDeviceId(Countly.deviceIDType["TemporaryDeviceID"], ON_SERVER);

Note: When passing TemporaryDeviceID for deviceID parameter, argument for onServerparameter diff --git a/flutter/next.md b/flutter/next.md index 1d5d7bda..623f86b8 100644 --- a/flutter/next.md +++ b/flutter/next.md @@ -15,61 +15,80 @@ 4.0, tvOS 10.0, macOS 10.14), and it requires Xcode 13.0+.

- For Android builds, this SDK requires a minimum Android version of 4.2.x (API - Level 17). + For Android builds, this SDK requires a minimum Android version of 5.0 (API Level + 21).

For Web builds, this SDK is compatible with browsers that support ECMAScript 5. Minimum versions of major internet browsers that fully support ES5 are:

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- IE - - Edge - - Firefox - - Firefox (Android) - - Opera - - Opera (Mobile) - - Safari - - Safari (iOS) - - Chrome - - Chrome (Android) -
101221961564662398
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ IE + + Edge + + Firefox + + Firefox (Android) + + Opera + +

+ Opera +

+

+ (Mobile) +

+
+ Safari + +

+ Safari +

+

+ (iOS) +

+
+ Chrome + +

+ Chrome +

+

+ (Android) +

+
101221961564662398
+

To examine the example integrations, please have a look here. @@ -78,19 +97,19 @@

Add this to your project's pubspec.yaml file:

-
dependencies:
+
dependencies:
   countly_flutter: ^25.4.0

After you can install packages from the command line with Flutter:

-
flutter pub get
+
flutter pub get

SDK Integration

Minimal Setup

The shortest way to initialize the SDK, if you want Countly SDK to take care of device ID seamlessly, is to use the code below.

-
// Create the configuration with your app key and server URL
+
// Create the configuration with your app key and server URL
 CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 
 // Initialize with that configuration
@@ -123,16 +142,16 @@ Countly.initWithConfig(config).then((value){
 

SDK Data Storage

SDK data storage locations are platform-specific:

    -
  • +
  • For iOS, the SDK data is stored in the Application Support Directory in a file named "Countly.dat"
  • -
  • +
  • For Android, the SDK data is stored in SharedPreferences. A SharedPreferences object points to a file containing key-value pairs and provides simple reading and writing methods.
  • -
  • +
  • For Web, the SDK data is stored in Local Storage. It can be modified to use memory only or cookies.
  • @@ -145,7 +164,7 @@ Countly.initWithConfig(config).then((value){

    We advise doing this while implementing Countly features in your application.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setLoggingEnabled(true);

    For more information on where to find the SDK logs you can check the documentation @@ -167,7 +186,7 @@ config.setLoggingEnabled(true);

    If you want to enable automatic unhandled crash reporting, you need to call this before init:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.enableCrashReporting()

    By doing that it will automatically catch all errors that are thrown from within @@ -184,7 +203,7 @@ config.enableCrashReporting()

    The following call will add the provided segmentation to all recorded crashes. Use the following function for this purpose:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setCustomCrashSegment(Map<String, Object> segments);

    Handled Exceptions

    @@ -195,17 +214,17 @@ config.setCustomCrashSegment(Map<String, Object> segments);

    be provided to the function. A potential use case would be to exception.toString()

    -
    Countly.logException(String exception, bool nonfatal, [Map<String, Object> segmentation])
    +
    Countly.logException(String exception, bool nonfatal, [Map<String, Object> segmentation])

    The issue is recorded with a provided Exception object. If no stacktrace is set,StackTrace.current will be used.

    -
    Countly.logExceptionEx(Exception exception, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})
    +
    Countly.logExceptionEx(Exception exception, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})

    The exception/error is recorded through a string message. If no stack trace is provided, StackTrace.current will be used.

    -
    Countly.logExceptionManual(String message, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})
    +
    Countly.logExceptionManual(String message, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})

    Below are some examples that how to log handled/nonfatal and unhandled/fatal exceptions manually. @@ -213,7 +232,7 @@ config.setCustomCrashSegment(Map<String, Object> segments);

1. Manually report exception

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal);
 
@@ -223,7 +242,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal);
 

2. Manually report exception with stack trace

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, stacktrace: STACK_TRACE_OBJECT);
 
@@ -233,7 +252,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, stacktrace: STACK_TRACE_O
 

3. Manually report exception with segmentation

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, segmentation: {"_facebook_version": "0.0.1"});
 
@@ -243,7 +262,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, segmentation: {"_facebook
 

4. Manually report exception with stack trace and segmentation

-
bool nonfatal = true; // Set it false in case of fatal exception
+
bool nonfatal = true; // Set it false in case of fatal exception
 // With Exception object
 Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, STACK_TRACE_OBJECT, {"_facebook_version": "0.0.1"});
 
@@ -257,7 +276,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_fa
   will be sent together with the crash report.
 

The following function call adds a crash breadcrumb:

-
Countly.addCrashLog(String logs)
+
Countly.addCrashLog(String logs)

Events

Event is any type of action that @@ -272,7 +291,7 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_fa In the SDK all event-related functionality can be browsed from the returned interface on:

-
Countly.instance.events
+
Countly.instance.events

When providing segmentation for events, the following primitive data types are supported: "String," "int," "double," and "bool." Additionally, Lists composed @@ -285,43 +304,43 @@ Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_fa of what information each usage will provide us:

    -
  • +
  • Usage 1: how many times a purchase event occurred.
  • -
  • +
  • Usage 2: how many times a purchase event occurred + the total amount of those purchases.
  • -
  • +
  • Usage 3: how many times a purchase event occurred + which countries and application versions those purchases were made from.
  • -
  • +
  • Usage 4: how many times a purchase event occurred + the total amount both of which are also available segmented into countries and application versions.
  • -
  • +
  • Usage 5: how many times purchase event occurred + the total amount both of which are also available segmented into countries and application versions + the total duration of those events (under Timed Events topic below).

The function signature as follows

-
Future<String?> recordEvent(String key, [Map<String, Object>? segmentation, int? count, double? sum, int? duration])
+
Future<String?> recordEvent(String key, [Map<String, Object>? segmentation, int? count, double? sum, int? duration])

1. Event key and count

-
Countly.instance.events.recordEvent('purchase', null, 1);
+
Countly.instance.events.recordEvent('purchase', null, 1);

2. Event key, count and sum

-
Countly.instance.events.recordEvent('purchase', null, 1, 0.99);
+
Countly.instance.events.recordEvent('purchase', null, 1, 0.99);
 

3. Event key and count with segmentation(s)

-
Map<String, Object>? segmentation = {
+
Map<String, Object>? segmentation = {
   'country': 'Germany',
   'app_version': '1.0',
   'rating': 10,
@@ -337,7 +356,7 @@ Countly.instance.events.recordEvent('purchase', segmentation, 1);
 

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

-
Map<String, Object>? segmentation = {
+
Map<String, Object>? segmentation = {
   'country': 'Germany',
   'app_version': '1.0',
   'rating': 10,
@@ -353,7 +372,7 @@ Countly.instance.events.recordEvent('purchase', segmentation, 1, 0.99);
 

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

-
Map<String, Object>? segmentation = {
+
Map<String, Object>? segmentation = {
   'country': 'Germany',
   'app_version': '1.0',
   'rating': 10,
@@ -370,7 +389,7 @@ Countly.instance.events.recordEvent('purchase', segmentation, 1, 0.99, 1);
 

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

-
// Basic event
+
// Basic event
 Countly.instance.events.startEvent("Timed Event");
 
 Timer timer = Timer(new Duration(seconds: 5), () {
@@ -378,9 +397,11 @@ Timer timer = Timer(new Duration(seconds: 5), () {
 });
 

- You may also provide additional information when ending an event. However, in that case, you have to provide the segmentation, count, and sum. The default values for those are "null", 1 and 0. + You may also provide additional information when ending an event. However, in + that case, you have to provide the segmentation, count, and sum. The default + values for those are "null", 1 and 0.

-
// Event with Segment, sum and count
+
// Event with Segment, sum and count
 Countly.instance.events.startEvent("Timed Event With Segment, Sum and Count");
 
 Timer timer = Timer(new Duration(seconds: 5), () {
@@ -400,7 +421,7 @@ Timer timer = Timer(new Duration(seconds: 5), () {
 

You may cancel the started timed event in case it is not relevant anymore:

-
//start some event
+
//start some event
 Countly.instace.events.startEvent(eventName);
 
 //wait some time
@@ -418,16 +439,16 @@ Countly.instance.events.cancelEvent(eventName);
   track a session automatically. This is how it works:
 

    -
  • +
  • Start/Begin session Request: It is sent to the server when the app comes back to the foreground from the background, and it includes basic metrics.
  • -
  • +
  • Update Session Request: It automatically sends a periodical (60 sec by default) update session request while the app is in the foreground.
  • -
  • +
  • End Session Request: It is sent at the end of a session when the app goes to the background or terminates.
  • @@ -444,22 +465,23 @@ Countly.instance.events.cancelEvent(eventName); relying on the SDK.

    It can be enabled during init with:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    -config.enableManualSessionHandling();
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +

    Afterwards it is up to the implementer to make calls to:

      -
    • Begin session (Starts a session)
    • -
    • +
    • Begin session (Starts a session)
    • +
    • Update session duration (By default, you would call this every 60 seconds after beginning a session so that it is not closed server side. If you would - want to increase that duration, you would have to increase the "Maximal Session Duration" in your server API configuration) + want to increase that duration, you would have to increase the "Maximal Session + Duration" in your server API configuration)
    • -
    • End session (Ends and updates duration)
    • +
    • End session (Ends and updates duration)

    You can use the 'sessions interface' to make these calls:

    -
    Countly.instance.sessions.beginSession();
    +
    Countly.instance.sessions.beginSession();
     Countly.instance.sessions.updateSession();
    -Countly.instance.sessions.endSession();
    +Countly.instance.sessions.endSession();

    View Tracking

    @@ -471,7 +493,7 @@ Countly.instance.sessions.endSession();

The SDK provides access to all view-related functionality through the interface returned by:

-
Countly.instance.views
+
Countly.instance.views

Manual View Recording

You can manually track views in your application.

@@ -488,12 +510,12 @@ Countly.instance.sessions.endSession();

If you want to start a view that will be automatically stopped when starting another view, use the following method:

-
// record a view on your application
-final String? viewID = await Countly.instance.views.startAutoStoppedView("Dashboard");
+
// record a view on your application
+final String? viewID = await Countly.instance.views.startAutoStoppedView("Dashboard");

You can also specify the custom segmentation key-value pairs while starting views:

-
Map<String, Object> segmentation = {
+
Map<String, Object> segmentation = {
   "country": "Germany",
   "app_version": "1.0",
   "rating": 10,
@@ -504,21 +526,21 @@ final String? viewID = await Countly.instance.views.startAutoStoppedViewstartAutoStoppedView("HomePage", segmentation);
+final String? anotherViewID = Countly.instance.views.startAutoStoppedView("HomePage", segmentation);
 

Regular Views

Opposed to "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 manually start a view using the startViewmethod with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName or stopViewWithID + can manually start a view using the startViewmethod with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName or stopViewWithID

-
// record a view on your application
+
// record a view on your application
 Countly.instance.views.startView("HomePage");
 final String? viewID = await Countly.instance.views.startView("Dashboard");

You can also specify the custom segmentation key-value pairs while starting views:

-
Map<String, Object> segmentation = {
+
Map<String, Object> segmentation = {
   "country": "Germany",
   "app_version": "1.0",
   "rating": 10,
@@ -537,30 +559,30 @@ final String? anotherViewID = Countly.instance.views.startView("HomePage", segme
   you try to stop one with that name, the SDK would close one of those randomly.
 

Below you can see example ways of stopping views.

-
Countly.instance.views.stopViewWithName("HomePage");
+
Countly.instance.views.stopViewWithName("HomePage");

This function allows you to manually stop the tracking of a view identified by its name. You can also specify the custom segmentation key-value pairs while stopping views:

-
Countly.instance.views.stopViewWithName("HomePage", segmentation);
+
Countly.instance.views.stopViewWithName("HomePage", segmentation);

You can also stop view tracking by its unique idetifier using - stopViewWithID + stopViewWithID

-
Countly.instance.views.stopViewWithID(viewID);
+
Countly.instance.views.stopViewWithID(viewID);

You can also specify the custom segmentation key-value pairs while stopping views:

-
Countly.instance.views.stopViewWithID(anotherViewID, segmentation);
+
Countly.instance.views.stopViewWithID(anotherViewID, segmentation);

You can stop all views tracking using - stopAllViews + stopAllViews

-
Countly.instance.views.stopAllViews();
+
Countly.instance.views.stopAllViews();

You can also specify the custom segmentation key-value pairs while stopping all views:

-
Countly.instance.views.stopAllViews(segmentation);
+
Countly.instance.views.stopAllViews(segmentation);

Pausing and Resuming Views

This SDK allows you to start multiple @@ -571,18 +593,19 @@ final String? anotherViewID = Countly.instance.views.startView("HomePage", segme

You can pause view tracking by its unique identifier using - pauseViewWithID + pauseViewWithID

-
Countly.instance.views.pauseViewWithID(viewID);
+
Countly.instance.views.pauseViewWithID(viewID);

- This function temporarily pauses the tracking of a view identified by its unique identifier. + This function temporarily pauses the tracking of a view identified by its unique + identifier.

- You can resume view tracking by its unique identifier using resumeViewWithID: + You can resume view tracking by its unique identifier using resumeViewWithID:

-
Countly.instance.views.resumeViewWithID(viewID);
+
Countly.instance.views.resumeViewWithID(viewID);

- This function resumes the tracking of a view identified by its unique identifier. + This function resumes the tracking of a view identified by its unique identifier.

Adding Segmentation to Started Views

@@ -599,7 +622,7 @@ final String? anotherViewID = Countly.instance.views.startView("HomePage", segme

Here is an example on how to achieve that using the view name:

-
String viewName = 'HomePage';
+
String viewName = 'HomePage';
 await Countly.instance.views.startView(viewName);
 
 Map<String, Object> segmentation = {
@@ -617,7 +640,7 @@ await Countly.instance.views.addSegmentationToViewWithName(viewName, segmentatio
 

Here is an example for how to add segmentation to a view using its ID:

-
String? viewID = await Countly.instance.views.startView('HomePage');
+
String? viewID = await Countly.instance.views.startView('HomePage');
 
 Map<String, Object> segmentation = {
   "country": "Germany",
@@ -642,7 +665,7 @@ await Countly.instance.views.addSegmentationToViewWithID(viewID!, segmentation);
   For setting global segmentation values during SDK initialization, use the following
   method:
 

-
// set global segmentation at initialization
+
// set global segmentation at initialization
 final CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setGlobalViewSegmentation(segmentation);

@@ -653,12 +676,12 @@ config.setGlobalViewSegmentation(segmentation);

ThesetGlobalViewSegmentation method will replace the previously set values..

-
Countly.instance.views.setGlobalViewSegmentation(segmentation);
+
Countly.instance.views.setGlobalViewSegmentation(segmentation);

The updateGlobalViewSegmentation method will modify the previously set values and overwrite any previously set keys.

-
Countly.instance.views.updateGlobalViewSegmentation(segmentation);
+
Countly.instance.views.updateGlobalViewSegmentation(segmentation);

Device ID Management

A device ID is a unique identifier for your users. You may specify the device @@ -669,7 +692,7 @@ config.setGlobalViewSegmentation(segmentation);

You may provide your custom device ID when initializing the SDK:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.setDeviceId(DEVICE_ID);

Changing the Device ID

@@ -680,7 +703,7 @@ config.setDeviceId(DEVICE_ID);

You may configure or change the device ID anytime using:

-
Countly.instance.deviceId.setID(DEVICE_ID);
+
Countly.instance.deviceId.setID(DEVICE_ID);

When using setID, the SDK determines internally if the device will be counted as a new device on the server or if it will merge the new and old @@ -706,7 +729,7 @@ config.setDeviceId(DEVICE_ID);

You can enable temporary device ID mode when initializing the SDK:

-
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
 config.enableTemporaryDeviceIDMode();
 
 // Initialize with that configuration
@@ -714,7 +737,7 @@ Countly.initWithConfig(config);

To enable a temporary device ID after initialization, you can call:

-
Countly.instance.deviceId.enableTemporaryIDMode();
+
Countly.instance.deviceId.enableTemporaryIDMode();

The SDK will be in temporary device ID mode, all requests will be on hold and they will be persistently stored. @@ -733,23 +756,20 @@ Countly.initWithConfig(config);

You may want to see what the current device ID is. For that, you can use the following call:

-
String? currentDeviceId = Countly.instance.deviceId.getID();
+
String? currentDeviceId = Countly.instance.deviceId.getID();

- You can use getIDType method which returns a - DeviceIDType to get the current device ID type. The ID type is an enum with the possible values of: + You can use getIDType method which returns a + DeviceIDType to get the current device ID type. The ID type is an + enum with the possible values of:

    -
  • - "DEVELOPER_SUPPLIED" - device ID was supplied by the host app. -
  • -
  • - "SDK_GENERATED" - device ID was generated by the SDK. -
  • -
  • - "TEMPORARY_ID" - the SDK is in temporary device ID mode. +
  • + "DEVELOPER_SUPPLIED" - device ID was supplied by the host app.
  • +
  • "SDK_GENERATED" - device ID was generated by the SDK.
  • +
  • "TEMPORARY_ID" - the SDK is in temporary device ID mode.
-
DeviceIdType? deviceIdType = await Countly.instance.deviceId.getIDType();
+
DeviceIdType? deviceIdType = await Countly.instance.deviceId.getIDType();

Device ID Generation

When the SDK is initialized for the first time with no device ID, it will generate @@ -792,7 +812,7 @@ Countly.initWithConfig(config); android/app/src/main/AndroidManifest.xml inside application tag.

-
<application ...>
+
<application ...>
 ...
   <service android:name="ly.count.dart.countly_flutter.CountlyMessagingService">
     <intent-filter>
@@ -804,7 +824,7 @@ Countly.initWithConfig(config);

Step 6: Add the following line in file android/build.gradle

-
buildscript {
+
buildscript {
   dependencies {
     classpath 'com.google.gms:google-services:LATEST'
     }
@@ -819,7 +839,7 @@ Countly.initWithConfig(config);

Step 7: Add the following line in file android/app/build.gradle

-
dependencies {
+
dependencies {
   implementation 'ly.count.android:sdk:LATEST'
   implementation 'com.google.firebase:firebase-messaging:LATEST'
 }
@@ -851,15 +871,15 @@ apply plugin: 'com.google.gms.google-services'
   For this purpose you can find CountlyNotificationService.h/m file
   under:
 

-
Pods/Development Pods/Countly/{PROJECT_NAME}/ios/.symlinks/plugins/countly_flutter/ios/Classes/CountlyiOS/CountlyNotificationService.h/m
+
Pods/Development Pods/Countly/{PROJECT_NAME}/ios/.symlinks/plugins/countly_flutter/ios/Classes/CountlyiOS/CountlyNotificationService.h/m

Some tips to find the files from deep hierarchy:

    -
  • +
  • You can filter the files in the navigator using a shortcut ⌥⌘J (Option-Command-J), in the filter box type "CountlyNotificationService" and it will show the related files only.
  • -
  • +
  • You can find the file using the shortcut ⇧⌘O (Shift-Command-O) and then navigate to that file using the shortcut ⇧⌘J (Shift-Command-J)
  • @@ -874,18 +894,18 @@ apply plugin: 'com.google.gms.google-services' token mode. This would allow you to choose either test or production modes, push token mode should be set before init.

    -
    // Set messaging mode for push notifications
    +
    // Set messaging mode for push notifications
     Countly.pushTokenType(Countly.messagingMode["TEST"]);

    When you are finally ready to initialise Countly push, you would call this:

    -
    // This method will ask for permission, enables push notification and send push token to countly server.
    +
    // This method will ask for permission, enables push notification and send push token to countly server.
     Countly.askForNotificationPermission();

    Also it is important to note that push notification is enabled for iOS by default, so to disable you need to call disablePushNotifications method:

    -
    // Disable push notifications feature for iOS, by default it is enabled.
    +
    // Disable push notifications feature for iOS, by default it is enabled.
     Countly.disablePushNotifications();

    Removing Push and Its Dependencies

    @@ -898,18 +918,18 @@ Countly.disablePushNotifications();

    To register a Push Notification callback after initializing the SDK, use the method below.

    -
    Countly.onNotification((String notification) {
    +
    Countly.onNotification((String notification) {
       print(notification);
    -});
    +});

    In order to listen to notification receive and click events, Place below code in AppDelegate.swift

    Add header files

    -
    import countly_flutter
    +
    import countly_flutter
     

    Add these methods:

    -
    // Required for the notification event. You must call the completion handler after handling the remote notification.
    +
    // Required for the notification event. You must call the completion handler after handling the remote notification.
     func application(application: UIApplication,  didReceiveRemoteNotification userInfo: [NSObject : AnyObject],  fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
       CountlyFlutterPlugin.onNotification(userInfo);
       completionHandler(.newData);
    @@ -938,7 +958,7 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec
       Here is the example of how data will receive in push callbacks:Screenshot_2022-06-24_at_7.04.23_PM.png
       Data Received for Android platform:
     

    -
    {
    +
    {
       "c.e.cc": "TR",
       "c.e.dt": "mobile",
       "Key": "value",
    @@ -950,9 +970,9 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec
       "sound": "custom",
       "title": "title",
       "message": "Message"
    -}
    +}

    Data Received for iOS platform:

    -
    {
    +
    {
       Key = value;
       aps = {
         alert = {
    @@ -973,7 +993,7 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec
         i = 62b5b945cabedb0870e9f217;
         l = "https://www.google.com/";
       };
    -}
    +}

    User Location

    @@ -996,23 +1016,21 @@ override func userNotificationCenter(\_ center: UNUserNotificationCenter, didRec When setting user location information, you would be setting these values:

      -
    • +
    • countryCode a string in ISO 3166-1 alpha-2 format country code
    • -
    • +
    • city a string specifying city name
    • -
    • +
    • location a string comma-separated latitude and longitude
    • -
    • +
    • IP a string specifying an IP address in IPv4 or IPv6 formats
    -

    - All values are optional, but at least one should be set. -

    -
    // Example for setLocation
    +

    All values are optional, but at least one should be set.

    +
    // Example for setLocation
     CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setLocation(country_code: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12')

    @@ -1020,7 +1038,7 @@ config.setLocation(country_code: 'TR', city: 'Istanbul', gpsCoordinates: '41.008 SDK has started. To do so, use the setUserLocation method as shown below.

    -
    // Example for setUserLocation
    +
    // Example for setUserLocation
     Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12');
     

    Disable Location

    @@ -1030,7 +1048,7 @@ Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41 setUserLocation is called with any non-null value, tracking will resume.

    -
    //disable location tracking
    +
    //disable location tracking
     Countly.disableLocation();

    Remote Config

    @@ -1050,9 +1068,7 @@ Countly.disableLocation();

    stay as they were. A previously valid key may return no value after a full download.

    -

    - Downloading Values -

    +

    Downloading Values

    Automatic Remote Config Triggers

    @@ -1067,17 +1083,17 @@ Countly.disableLocation();

    The automatic download triggers that would trigger a full value download are:

      -
    • +
    • when the SDK has finished initializing
    • -
    • +
    • after the device ID is changed without merging
    • -
    • +
    • when user gets out of temp ID mode
    • -
    • - when CountlyConsent.remoteConfig consent is given after it had been removed before (if consents are enabled) +
    • + when CountlyConsent.remoteConfig consent is given after it had been removed before (if consents are enabled)

    @@ -1085,7 +1101,7 @@ Countly.disableLocation();

    enableRemoteConfigAutomaticTriggers on the configuration object you will provide during init.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    +  
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
       ..enableRemoteConfigAutomaticTriggers(); // necessary to enable the feature
     

    @@ -1094,33 +1110,33 @@ Countly.disableLocation();

    were not updated, you would have metadata indicating if a value belongs to the old or current user.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    -  ..enableRemoteConfigValueCaching(); 
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    +  ..enableRemoteConfigValueCaching(); 

    Manually 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, omitting (everything except) keys.

    Each of these calls also has an optional parameter that you can provide a RCDownloadCallback to, which would be triggered when the download attempt has finished.

    - dowloadAllKeys is the same as the automatically triggered 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 automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones).

    - Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array. + Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array.

    - Or you might want to update all the values except a few defined keys. To do so,  call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array. + Or you might want to update all the values except a few defined keys. To do so,  call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array.

    @@ -1129,7 +1145,7 @@ Countly.disableLocation();
    Omit Keys
    -
    Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) {
    +    
    Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) {
       if (rResult == RequestResult.Success) {
         // do sth
       } else {
    @@ -1138,7 +1154,7 @@ Countly.disableLocation();
    });

    - When making requests with an "inclusion" or "exclusion" 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 "inclusion" or "exclusion" 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

    @@ -1165,10 +1181,10 @@ Countly.disableLocation();

    key. This returns an Future<RCData> object that contains the value of the key and the metadata about that value's owner. If value in RCData was null - then no value was found or the value was null. + then no value was found or the value was null.  

    -
    Object? value_1 = await Countly.instance.remoteConfig.getValue("key_1").value;
    +
    Object? value_1 = await Countly.instance.remoteConfig.getValue("key_1").value;
     Object? value_2 = await Countly.instance.remoteConfig.getValue("key_2").value;
     Object? value_3 = await Countly.instance.remoteConfig.getValue("key_3").value;
     Object? value_4 = await Countly.instance.remoteConfig.getValue("key_4").value;
    @@ -1182,10 +1198,10 @@ JSONObject jObj = value4 as JSONObject;
       If you want to get all values together you can use
       getAllValues which returns a Future<Map<String,
       RCData>>.
    -  The SDK does not know the returned value type, so, it will return the Object. The developer then needs to cast it to the appropriate type. The returned values may also be JSONArrayJSONObject,
    +  The SDK does not know the returned value type, so, it will return the Object. The developer then needs to cast it to the appropriate type. The returned values may also be JSONArrayJSONObject,
       or just a simple value, such as int.
     

    -
    Map<String, RCData> allValues = await Countly.instance.remoteConfig.getAllValues();
    +
    Map<String, RCData> allValues = await Countly.instance.remoteConfig.getAllValues();
     
     int intValue = allValues["key_1"] as int;
     double doubleValue = allValues["key_2"] as double;
    @@ -1197,7 +1213,7 @@ JSONObject jObj = allValues["key_4"] as JSONObject;
    belongs to. The isCurrentUsersData is only false when there was a device ID change, but somehow (or intentionally) a remote config value was not updated.

    -
    Class RCData {
    +
    Class RCData {
       Object value;
       Boolean isCurrentUsersData;
     }
    @@ -1211,14 +1227,14 @@ JSONObject jObj = allValues["key_4"] as JSONObject;

    At some point, you might like to erase all the values downloaded from the server. You will need to call one function to do so.

    -
    Countly.instance.remoteConfig.clearAll();
    +
    Countly.instance.remoteConfig.clearAll();

    Global Download Callbacks

    Also, you may provide a global callback function to be informed when the remote config download request is finished with remoteConfigRegisterGlobalCallback during the SDK initialization:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
       ..remoteConfigRegisterGlobalCallback((rResult, error, fullValueUpdate, downloadedValues) {
         if (error != null) {
           // do sth
    @@ -1230,24 +1246,24 @@ JSONObject jObj = allValues["key_4"] as JSONObject;
    and it would have the following parameters:

      -
    • +
    • rResult: RequestResult Enum (either - Error, Success or NetworkIssue) + Error, Success or NetworkIssue)
    • -
    • +
    • error: String (error message. "null" if there is no error)
    • -
    • +
    • fullValueUpdate: boolean ("true" - all values updated, "false" - a subset of values updated)
    • -
    • +
    • downloadedValues: Map<String, RCData> (the whole downloaded remote config values)
    -
    RCDownloadCallback {
    +
    RCDownloadCallback {
       void callback(RequestResult rResult, String error, boolean fullValueUpdate, Map<String, RCData> downloadedValues)
     }
     
    @@ -1261,7 +1277,7 @@ JSONObject jObj = allValues["key_4"] as JSONObject;
    You can also register (or remove) callbacks to do different things after the SDK initialization. You can register these callbacks multiple times:

    -
    // register a callback
    +
    // register a callback
     Countly.instance.remoteConfig.registerDownloadCallback((rResult, error, fullValueUpdate, downloadedValues) {
       // do sth
     });
    @@ -1276,36 +1292,34 @@ Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueU
       from some or all existing A/B tests available.
     

    -

    -  Enrollment on Download -

    +

     Enrollment on Download

    You can enroll into the A/B tests automatically whenever you download RC values from the server. To do so you have to set the following flag at the config object during initialization:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    -..enrollABOnRCDownload();
    -

    -  Enrollment on Access -

    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
    +..enrollABOnRCDownload();
    +

     Enrollment on Access

    - You can also enroll to A/B tests while getting RC values from storage. You can use getValueAndEnroll while getting a single value and getAllValuesAndEnroll while getting all values to enroll to the keys that exist. If no value was stored for those keys these functions would not enroll the user. Both of these functions works the same way with their non-enrolling variants, namely; getValue and getAllValues. + You can also enroll to A/B tests while getting RC values from storage. You + can use getValueAndEnroll while getting a single value and + getAllValuesAndEnroll while getting all values to enroll to + the keys that exist. If no value was stored for those keys these functions + would not enroll the user. Both of these functions works the same way with + their non-enrolling variants, namely; getValue and + getAllValues.

    -

    -  Enrollment on Action -

    +

     Enrollment on Action

    To enroll a user into the A/B tests for the given keys you use the following method:

    -
    Countly.instance.remoteConfig.enrollIntoABTestsForKeys(List<String> keys);
    +
    Countly.instance.remoteConfig.enrollIntoABTestsForKeys(List<String> keys);

    Here the keys array is the mandatory parameter for this method to work.

    -

    - Exiting A/B Tests -

    +

    Exiting A/B Tests

    Platform Info
    @@ -1317,7 +1331,7 @@ Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueU If you want to remove users from A/B tests of certain keys you can use the following function:

    -
    Countly.instance.remoteConfig.exitABTestsForKeys(List<String> keys);
    +
    Countly.instance.remoteConfig.exitABTestsForKeys(List<String> keys);

    Here if no keys are provided it would remove the user from all A/B tests instead.

    @@ -1349,12 +1363,12 @@ Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueU dashboard. If the user dismisses star rating dialog without giving a rating, the event will not be recorded.

    -
    Countly.askForStarRating();
    +
    Countly.askForStarRating();

    The star-rating dialog's title, message, and dismiss button text may be customized through the following functions:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setStarRatingTextTitle("Custom title"); // Only available for Android
     config.setStarRatingTextMessage("Custom message");
     config.setStarRatingTextDismiss("Custom message"); // Only available for Android
    @@ -1368,10 +1382,10 @@ config.setStarRatingTextDismiss("Custom message"); // Only available for Android

    It is possible to display 3 kinds of feedback widgets: - NPS, - Survey, + NPS, + Survey, and - Rating. + Rating.

    For more detailed information about Feedback Widgets, you can refer to @@ -1387,12 +1401,12 @@ config.setStarRatingTextDismiss("Custom message"); // Only available for Android After you have created widgets on your dashboard, you can reach the methods to show them from the feedback interface of your Countly instance:

    -
    Countly.instance.feedback
    +
    Countly.instance.feedback

    You can display a random active widget for the widget type you want with one of these methods:

    -
    .presentNPS([String? nameIDorTag, FeedbackCallback? feedbackCallback])
    +
    .presentNPS([String? nameIDorTag, FeedbackCallback? feedbackCallback])
     .presentRating([String? nameIDorTag, FeedbackCallback? feedbackCallback])
     .presentSurvey([String? nameIDorTag, FeedbackCallback? feedbackCallback])
     
    @@ -1420,7 +1434,7 @@ Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback(
     

    For more in-depth information on retrieving feedback widgets, understanding object structures, or presenting them yourself, please refer to the following - resource. + resource.

    Manual Reporting

    @@ -1445,7 +1459,7 @@ Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback( In case you want to use with callback then you can call 'getFeedbackWidgetData' in this way:

    -
    Countly.getFeedbackWidgetData(chosenWidget, onFinished: (retrievedWidgetData, error) {
    +
    Countly.getFeedbackWidgetData(chosenWidget, onFinished: (retrievedWidgetData, error) {
       if (error == null) {
       }
     });
    @@ -1453,7 +1467,7 @@ Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback( If you want to use it without a callback then you can call 'getFeedbackWidgetData' in this way:

    -
    List result = await Countly.getFeedbackWidgetData(chosenWidget);
    +
    List result = await Countly.getFeedbackWidgetData(chosenWidget);
     String? error = result[1];
     if (error == null) {
       Map<String, dynamic> retrievedWidgetData = result[0];
    @@ -1468,7 +1482,7 @@ if (error == null) {
       use it, the widgetInformation and the widgetData to report the feedback result
       with the following call:
     

    -
    //this contains the reported results
    +
    //this contains the reported results
     Map<String, Object> reportedResult = {};
     
     //
    @@ -1523,25 +1537,25 @@ Countly.reportFeedbackWidgetManually(chosenWidget, retrievedWidgetData , reporte
       Using the following call, you can set both the predefined and the custom user
       properties during initialization:
     

    -
    var userProperties = {
    +
    var userProperties = {
       "customProperty": "custom Value",
       "username": "USER_NAME",
       "email": "USER_EMAIL"
     };
     CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setUserProperties(userProperties); 
    -

    Setting User profile values

    +

    Setting User profile values

    The following calls can be used after init.

    If you want to set a single property, you can call Countly.instance.userProfile.setProperty(key, value)

    -
    Countly.instance.userProfile.setProperty("specialProperty", "value");
    +
    Countly.instance.userProfile.setProperty("specialProperty", "value");

    If you want to set multiple properties at the same time, you can use: Countly.instance.userProfile.setUserProperties(userProperties)

    -
    // example for setting user data
    +
    // example for setting user data
     Map<String, Object> userProperties= {
       "name": "Nicola Tesla",
       "username": "nicola",
    @@ -1574,7 +1588,7 @@ Countly.instance.userProfile.setUserProperties(userProperties);
    the same property.

    Below is the list of available methods:

    -
    //increment used value by 1
    +
    //increment used value by 1
     Countly.instance.userProfile.increment("increment");
     //increment used value by provided value
     Countly.instance.userProfile.incrementBy("incrementBy", 10);
    @@ -1606,7 +1620,7 @@ Countly.instance.userProfile.pull("type", "morning");
    to start using them you would first need to enable them and give the required consent if it was required:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     
     // this interface exposes the available APM features and their modifications.
     config.apm. 
    @@ -1625,9 +1639,9 @@ config.apm.
    with the same key.

    To start a custom trace, use:

    -
    Countly.startTrace(traceKey);
    +
    Countly.startTrace(traceKey);

    To end a custom trace, use:

    -
    String traceKey = "Trace Key";
    +
    String traceKey = "Trace Key";
     Map<String, int> customMetric = {
       "ABC": 1233,
       "C44C": 1337
    @@ -1643,7 +1657,7 @@ Countly.endTrace(traceKey, customMetric);
    for your selected approach of making network requests and then call this after your network request is done:

    -
    Countly.recordNetworkTrace(networkTraceKey, responseCode, requestPayloadSize, responsePayloadSize, startTime, endTime);
    +
    Countly.recordNetworkTrace(networkTraceKey, responseCode, requestPayloadSize, responsePayloadSize, startTime, endTime);

    networkTraceKey is a unique identifier of the API endpoint you are targeting or just the url you are targeting, all params should be stripped. You @@ -1657,8 +1671,8 @@ Countly.endTrace(traceKey, customMetric);

    These are:

      -
    • App Start Time
    • -
    • App Background and Foreground time
    • +
    • App Start Time
    • +
    • App Background and Foreground time

    Tracking of these metrics are disabled by default and must be explicitly enabled @@ -1668,10 +1682,10 @@ Countly.endTrace(traceKey, customMetric);

    For tracking app start time automatically you will need to enable it in SDK init config:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     
     // enable it here separately with 'apm' interface.
    -config.apm.enableAppStartTimeTracking();
    +config.apm.enableAppStartTimeTracking();

    This calculates and records the app launch time for performance monitoring.

    @@ -1680,10 +1694,10 @@ config.apm.enableAppStartTimeTracking();
    will have to enable the usage of manual triggers together with enableAppStartTimeTracking during init:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     
     // enable it here separately with 'apm' interface.
    -config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger();
    +config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger();

    Now you can call Countly.appLoadingFinished() any time after SDK initialization to record that moment as the end of app launch time. @@ -1696,21 +1710,21 @@ config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger -

    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     
     // generate the timestamp you want (or you can directly pass a ts)
     int ts = DateTime.now().millisecondsSinceEpoch - 500; // 500 ms ago as an example
     
     // this would also work with manual trigger
    -config.apm.enableAppStartTimeTracking().setAppStartTimestampOverride(ts);
    +config.apm.enableAppStartTimeTracking().setAppStartTimestampOverride(ts);

    Lastly if you want to enable the SDK to record the time an app is in foreground or background automatically you would need to enable this option during init:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     
     // enable it here separately with 'apm' interface.
    -config.apm.enableForegroundBackgroundTracking();
    +config.apm.enableForegroundBackgroundTracking();

    User Consent

    For compatibility with data protection regulations, such as GDPR, the Countly @@ -1724,26 +1738,26 @@ config.apm.enableForegroundBackgroundTracking();

    Currently, available features with consent control are as follows:

      -
    • +
    • sessions - tracking when, how often and how long users use your app.
    • -
    • events - allow sending events to the server.
    • -
    • views - allow tracking which views user visits.
    • -
    • location - allow sending location information.
    • -
    • crashes - allow tracking crashes, exceptions and errors.
    • -
    • +
    • events - allow sending events to the server.
    • +
    • views - allow tracking which views user visits.
    • +
    • location - allow sending location information.
    • +
    • crashes - allow tracking crashes, exceptions and errors.
    • +
    • attribution - allow tracking from which campaign did user come.
    • -
    • +
    • users - allow collecting/providing user information, including custom properties.
    • -
    • push - allow push notifications
    • -
    • starRating - allow sending their rating and feedback
    • -
    • apm - allow application performance monitoring
    • -
    • +
    • push - allow push notifications
    • +
    • starRating - allow sending their rating and feedback
    • +
    • apm - allow application performance monitoring
    • +
    • remoteConfig - allows downloading remote config values from your server
    • -
    • +
    • content - allow to enter content zone to receive contents from the server.
    @@ -1752,7 +1766,7 @@ config.apm.enableForegroundBackgroundTracking();
    By default the requirement for consent is disabled. To enable it, you have to call setRequiresConsent with true, before initializing Countly.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setRequiresConsent(true);

    By default, no consent is given. That means that if no consent is enabled, Countly @@ -1766,8 +1780,9 @@ config.setRequiresConsent(true);

    of consent values. Or, you can use giveAllConsentsfor all consent values.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    -config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, CountlyConsent.attribution, CountlyConsent.push, CountlyConsent.events, CountlyConsent.views, CountlyConsent.crashes, CountlyConsent.users, CountlyConsent.push, CountlyConsent.starRating, CountlyConsent.apm, CountlyConsent.feedback, CountlyConsent.remoteConfig, CountlyConsent.content])
    config.giveAllConsents()
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, CountlyConsent.attribution, CountlyConsent.push, CountlyConsent.events, CountlyConsent.views, CountlyConsent.crashes, CountlyConsent.users, CountlyConsent.push, CountlyConsent.starRating, CountlyConsent.apm, CountlyConsent.feedback, CountlyConsent.remoteConfig, CountlyConsent.content])
    +config.giveAllConsents()

    The Countly SDK does not persistently store the status of given consents except push notifications. You are expected to handle receiving consent from end-users @@ -1785,7 +1800,7 @@ config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, Coun To reflect these changes in the Countly SDK, you can use the removeConsent or giveConsent methods.

    -
    //give consent values after init
    +
    //give consent values after init
     Countly.giveConsent([CountlyConsent.events, CountlyConsent.views, CountlyConsent.starRating, CountlyConsent.crashes]);
     
     //remove consent values after init
    @@ -1794,7 +1809,7 @@ Countly.removeConsent([CountlyConsent.events, CountlyConsent.views, CountlyConse
     

    You can also either give or remove consent to all possible SDK features:

    -
    //give consent to all features
    +
    //give consent to all features
     Countly.giveAllConsent();
     
     //remove consent from all features
    @@ -1809,7 +1824,7 @@ Countly.removeAllConsent();
    is set, all requests would be checked for the validity of &checksum field before being processed.

    -
    // sending data with salt
    +
    // sending data with salt
     CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setParameterTamperingProtectionSalt("salt");

    @@ -1827,70 +1842,81 @@ config.setParameterTamperingProtectionSalt("salt");

    Here is the list of functionalities "CountlyConfig" provides:

      -
    • - Device Id - A device ID is a unique identifier for your users. You may specify the device ID yourself or allow the SDK to generate it. +
    • + Device Id - A + device ID is a unique identifier for your users. You may specify the device + ID yourself or allow the SDK to generate it.
    • -
    • - Enable Logging - - To enable countly internal debugging logs. +
    • + Enable Logging - + To enable countly internal debugging logs.
    • -
    • - Enable Crash Reporting - +
    • + Enable Crash Reporting - To enable uncaught crash reporting.
    • -
    • - Salt - +
    • + Salt - Set the optional salt to be used for calculating the checksum of requested - data which will be sent with each request. + data which will be sent with each request.
    • -
    • - Event queue threshold - +
    • + Event queue threshold - Set the threshold for event grouping. Event count that is bellow the threshold - will be sent on update ticks. + will be sent on update ticks.
    • -
    • +
    • Update Session Timer - Sets the interval for the automatic session update calls.
    • -
    • - Custom Crash Segment -Set +
    • + Custom Crash Segment -Set custom crash segmentation which will be added to all recorded crashes.
    • -
    • +
    • User consent - Set if consent should be required and give consents.
    • -
    • - Forcing HTTP POST - When set to true, all requests made to the Countly server will be done using HTTP POST. Otherwise, the SDK sends all requests using the HTTP GET method. In some cases, if the data to be sent exceeds the 1800-character limit, the SDK uses the POST method. The default value is false. +
    • + Forcing HTTP POST - When + set to true, all requests made to the Countly server + will be done using HTTP POST. Otherwise, the SDK sends all requests using + the HTTP GET method. In some cases, if the data to be sent exceeds the 1800-character + limit, the SDK uses the POST method. The default value is false.
    • -
    • - Star Rating Text - Set shown title, message and dismiss buttim text for the star rating dialogs. +
    • + Star Rating Text - + Set shown title, message and dismiss buttim text for the star rating dialogs. (no Web platform support)
    • -
    • - Application Performance Monitoring - Enable APM features, which includes the recording of app start time. +
    • + Application Performance Monitoring - + Enable APM features, which includes the recording of app start time. (no Web platform support)
    • -
    • - Set User Location - Set user location manually instead of using Countly server to use GeoIP database to deduce a user's location. +
    • + Set User Location - + Set user location manually instead of using Countly server to use GeoIP database + to deduce a user's location.
    • -
    • - Max Queue Size Limit - Set maximum size for the request queue. +
    • + Max Queue Size Limit - Set + maximum size for the request queue.
    • -
    • - Manual Sessions - To enable manual session handling - (no Web platform support) +
    • + Manual Sessions - + To enable manual session handling (no Web platform support)
    • -
    • - Automatic Remote Config - If +
    • + Automatic Remote Config - If enabled, will automatically download newest remote config values.
    • -
    • - Direct Attribution - +
    • + Direct Attribution - Report direct user attribution (no Web platform support)
    • -
    • - Indirect Attribution - +
    • + Indirect Attribution - Report indirect user attribution (no Web platform support)
    @@ -1900,7 +1926,7 @@ config.setParameterTamperingProtectionSalt("salt");
    example application. It assumes Flutter is installed in your system:

    -
    # clone the Countly SDK repository
    +
    # clone the Countly SDK repository
     git clone https://github.com/Countly/countly-sdk-flutter-bridge.git
     
     # dive into the cloned repo
    @@ -1939,7 +1965,7 @@ flutter run
    If you do not specify a value for the setMaxRequestQueueSize flag, the default setting of 1,000 will be used.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setMaxRequestQueueSize(5000);

    SDK Internal Limits

    @@ -1953,7 +1979,7 @@ config.setMaxRequestQueueSize(5000);

    Limits the maximum size of all user set keys (default: 128 chars):

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.sdkInternalLimits.setMaxKeyLength(int MAX_KEY_LENGTH);
     await Countly.initWithConfig(config);

    Value Size

    @@ -1961,14 +1987,14 @@ await Countly.initWithConfig(config);
    Limits the size of all user set string segmentation (or their equivalent) values (default: 256 chars):

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.sdkInternalLimits.setMaxValueSize(int MAX_VALUE_SIZE);
     await Countly.initWithConfig(config);

    Segmentation Values

    Limits the amount of user set segmentation key-value pairs (default: 100 entries):

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.sdkInternalLimits.setMaxSegmentationValues(int MAX_SEGMENTATION_COUNT);
     await Countly.initWithConfig(config);

    Breadcrumb Count

    @@ -1976,21 +2002,21 @@ await Countly.initWithConfig(config);
    Limits the amount of user set breadcrumbs that can be recorded (default: 100 entries, exceeding this deletes the oldest one):

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.sdkInternalLimits.setMaxBreadcrumbCount(int MAX_BREADCRUMB_COUNT);
     await Countly.initWithConfig(config);

    Stack Trace Lines Per Thread

    Limits the stack trace lines that would be recorded per thread (default: 30 lines):

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.sdkInternalLimits.setMaxStackTraceLinesPerThread(int MAX_STACK_THREAD);
     await Countly.initWithConfig(config);

    Stack Trace Line Length

    Limits the characters that are allowed per stack trace line (default: 200 chars):

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.sdkInternalLimits.setMaxStackTraceLineLength(int MAX_STACK_LENGTH);
     await Countly.initWithConfig(config);

    Attribution

    @@ -2006,11 +2032,9 @@ await Countly.initWithConfig(config);
    from specific campaigns. This feature is available for the Enterprise Edition.

    - There are 2 forms of attribution: direct Attribution and indirect Attribution. + There are 2 forms of attribution: direct Attribution and indirect Attribution.

    -

    - Direct Attribution -

    +

    Direct Attribution

    Platform Info
    @@ -2024,16 +2048,17 @@ await Countly.initWithConfig(config);

    object.

    - You can use recordDirectAttribution to set attribution values during initialization. + You can use recordDirectAttribution to set attribution values during + initialization.

    -
    String campaignData = 'JSON_STRING';
    +
    String campaignData = 'JSON_STRING';
     CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.recordDirectAttribution('CAMPAIN_TYPE', campaignData);

    You can also use recordDirectAttribution function to manually report attribution later:

    -
    String campaignData = 'JSON_STRING';
    +
    String campaignData = 'JSON_STRING';
     Countly.recordDirectAttribution('CAMPAIN_TYPE', campaignData);

    Currently this feature is limited and accepts data only in a specific format @@ -2041,20 +2066,19 @@ Countly.recordDirectAttribution('CAMPAIN_TYPE', campaignData);

    attribution. The data also needs to be formatted in a specific way. Either with the campaign id or with the campaign id and campaign user id.

    -
    String campaignData = '{cid:"[PROVIDED_CAMPAIGN_ID]", cuid:"[PROVIDED_CAMPAIGN_USER_ID]"}';
    +
    String campaignData = '{cid:"[PROVIDED_CAMPAIGN_ID]", cuid:"[PROVIDED_CAMPAIGN_USER_ID]"}';
     Countly.recordDirectAttribution('countly', campaignData);
    -

    - Indirect Attribution -

    +

    Indirect Attribution

    This feature would be used to report things like advertising ID's. For each platform those would be different values. For the most popular keys we have a class with predefined values to use, it is called "AttributionKey".

    - You can use recordDirectAttribution to set attribution values during initialization. + You can use recordDirectAttribution to set attribution values during + initialization.

    -
    Map<String, String> attributionValues = {};
    +
    Map<String, String> attributionValues = {};
     if(Platform.isIOS){
       attributionValues[AttributionKey.IDFA] = 'IDFA';
     }
    @@ -2063,12 +2087,12 @@ else {
     }
     
     CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    -config.recordIndirectAttribution(attributionValues);
    +config.recordIndirectAttribution(attributionValues);

    You can also use recordIndirectAttribution function to manually report attribution later

    -
    Map<String, String> attributionValues = {};
    +
    Map<String, String> attributionValues = {};
     if(Platform.isIOS){
       attributionValues[AttributionKey.IDFA] = 'IDFA';
     }
    @@ -2090,14 +2114,14 @@ Countly.recordIndirectAttribution(attributionValues);
    can use the same function later in the app's life cycle to disable the override. This function has to be called every time the app starts.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setHttpPostForced(true); // default is false

    Setting Custom Network Request Headers

    If you need to include custom network request headers in the requests sent by the SDK, you can easily add them using the following method.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setCustomNetworkRequestHeaders({'customHeaderKey': 'customHeaderValue'});

    This allows you to specify any headers your application requires for enhanced @@ -2116,7 +2140,7 @@ config.setCustomNetworkRequestHeaders({'customHeaderKey': 'customHeaderValue'}); 1. You can replace all requests with a different app key with the current app key:

    -
    //Replaces all requests with a different app key with the current app key.
    +
    //Replaces all requests with a different app key with the current app key.
     Countly.replaceAllAppKeysInQueueWithCurrentAppKey();

    In the request queue, if there are any requests whose app key is different than @@ -2124,7 +2148,7 @@ Countly.replaceAllAppKeysInQueueWithCurrentAppKey();

    app key. 2. You can remove all requests with a different app key in the request queue:

    -
    //Removes all requests with a different app key in request queue.
    +
    //Removes all requests with a different app key in request queue.
     Countly.removeDifferentAppKeysFromQueue();

    In the request queue, if there are any requests whose app key is different than @@ -2136,7 +2160,7 @@ Countly.removeDifferentAppKeysFromQueue();

    when the server is slow or unresponsive. This helps reduce server load and avoid unnecessary retries. It’s enabled by default but can be disabled if needed.

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.disableBackoffMechanism();

    For a detailed explanation of how the backoff mechanism works and when it triggers, @@ -2156,7 +2180,7 @@ config.disableBackoffMechanism();

    you don't want to get data older than a certain timeframe, you can configure the SDK to drop old requests:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setRequestDropAgeHours(10); // a positive integer indicating hours

    By using the setRequestDropAgeHours method while configuring the @@ -2171,31 +2195,31 @@ config.setRequestDropAgeHours(10); // a positive integer indicating hours event count reaches a threshold. By default it is 10. If you would like to change this, call:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setEventQueueSizeToSend(6);

    Checking if the SDK has been initialized

    In case you would like to check if init has been called, you may use the following function:

    -
    Countly.isInitialized();
    +
    Countly.isInitialized();

    Server Configuration

    Server Configuration is enabled by default. Changes made on SDK Manager SDK Configuration on your server will affect SDK behavior directly.

    -p> - In all cases, the configuration may not be applied during the app’s first run. +

    + p In all cases, the configuration may not be applied during the app’s first run. If this is a security sensitive case for the situations, you can provide the server config to the SDK during initialization.

    -
    config.setSDKBehaviorSettings("json server config")
    +
    config.setSDKBehaviorSettings("json server config")

    If you want to disable automatic config updates from the server, you can prevent the SDK from making server configuration fetch requests. This is useful if you're trying to reduce network traffic or control request counts.

    -
    config.disableSDKBehaviorSettingsUpdates()
    +
    config.disableSDKBehaviorSettingsUpdates()

    Content Zone

    The Content Zone feature enhances user engagement by delivering various types @@ -2206,7 +2230,7 @@ p>

    To start fetching content from the server, use the following method:

    -
    Countly.instance.content.enterContentZone()
    +
    Countly.instance.content.enterContentZone()

    This call will retrieve and display any available content for the user. It will also regularly check if a new content is available, and if it is, will fetch @@ -2216,16 +2240,16 @@ p> This regular check happens in every 30 seconds by default. It could be configurable while initializing the SDK through and it must be greater than 15 seconds.

    -
    countlyConfig.content.setZoneTimerInterval(60) //in seconds
    +
    countlyConfig.content.setZoneTimerInterval(60) //in seconds

    If you need to ask for content after a trigger you know you can use this method:

    -
    Countly.instance.content.refreshContentZone()
    +
    Countly.instance.content.refreshContentZone()

    When you want to exit from content zone and stop SDK from checking for available content you can use this method:

    -
    Countly.instance.content.exitContentZone()
    +
    Countly.instance.content.exitContentZone()

    To get informed when a user closes a content you can register a global content callback during SDK initialization: @@ -2236,8 +2260,8 @@ p> This feature is not supported in the Web platform.

    -
    countlyConfig.content.setGlobalContentCallback((contentStatus, contentData))
    -
    typedef ContentCallback = void Function(ContentStatus contentStatus, Map<String, dynamic> contentData);
    +
    countlyConfig.content.setGlobalContentCallback((contentStatus, contentData))
    +
    typedef ContentCallback = void Function(ContentStatus contentStatus, Map<String, dynamic> contentData);

    The `contentStatus` will indicate either `ContentStatus.completed` or `ContentStatus.closed`.

    @@ -2253,43 +2277,39 @@ p> for enabling advanced features like view name recording and visibility tracking. These features are currently in a testing phase and might change in future versions.

    -
    CountlyConfig config = CountlyConfig(COUNTLY_APP_KEY, COUNTLY_SERVER_URL);
    +
    CountlyConfig config = CountlyConfig(COUNTLY_APP_KEY, COUNTLY_SERVER_URL);
     config.experimental.enablePreviousNameRecording().enableVisibilityTracking();

    This class allows enabling two experimental features:

      -
    • Previous Name Recording
    • -
    • Visibility Tracking
    • +
    • Previous Name Recording
    • +
    • Visibility Tracking

    When you enable previous name recording, it will add previous view name to the view segmentations (cly_pvn) and previous event name to the event segmentations (cly_pen).

    -
    config.experimental.enablePreviousNameRecording()
    +
    config.experimental.enablePreviousNameRecording()

    When you enable visibility tracking, it will add a parameter (cly_v) to each recorded event's segmentation about the visibility of the app at the time of its recording.

    -
    config.experimental.enableVisibilityTracking()
    +
    config.experimental.enableVisibilityTracking()

    A/B Experiment Testing

    -

    - Variant Level Control -

    +

    Variant Level Control

    Platform Info
    This feature is not supported in the Web platform.

    -

    - Downloading -

    +

    Downloading

    You can fetch a map of all A/B testing parameters (keys) and variants associated with it:

    -
    Countly.instance.remoteConfig.testingDownloadVariantInformation((rResult, error){
    +
    Countly.instance.remoteConfig.testingDownloadVariantInformation((rResult, error){
       // do sth
     })

    @@ -2298,91 +2318,81 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking(); -

    - Accessing -

    +

    Accessing

    When test variants are fetched, they are saved to the memory. If the memory is erased, you must fetch the variants again. So a common flow is to use the fetched values right after fetching them. To access all fetched values, you can use:

    -
    Countly.sharedInstance().remoteConfig().testingGetAllVariants()
    +
    Countly.sharedInstance().remoteConfig().testingGetAllVariants()

    This would return a Future<Map<String, List<String>>> where a test's parameter is associated with all variants under that parameter. The parameter would be the key, and its value would be a String List of variants. For example:

    -
    {
    +
    {
       "key_1" : ["variant_1", "variant_2"],
       "key_2" : ["variant_3"]
     }
     

    Or instead you can get the variants of a specific key:

    -
    Countly.sharedInstance().remoteConfig().testingGetVariantsForKey(String valueKey)
    +
    Countly.sharedInstance().remoteConfig().testingGetVariantsForKey(String valueKey)

    This would only return a Future<List<String>> of variants for that specific key. If no variants were present for a key, it would return an empty list. A typical result would look like this:

    -
    ["variant_1", "variant_2"]
    +
    ["variant_1", "variant_2"]
     
    -

    - Enrolling / Exiting -

    +

    Enrolling / Exiting

    After fetching A/B testing parameters and variants from your server, you next would like to enroll the user to a specific variant. To do this, you can use the following method:

    -
    Countly.instance.remoteConfig.testingEnrollIntoVariant(String keyName, String variantName, void Function(RequestResult, String?)? callback)
    +
    Countly.instance.remoteConfig.testingEnrollIntoVariant(String keyName, String variantName, void Function(RequestResult, String?)? callback)

    Here the 'valueKey' would be the parameter of your A/B test, and 'variantName' is the variant you have fetched and selected to enroll for. The callback function is optional and works the same way as explained above in the Fetching Test Variants section.

    -

    - Experiment Level Control -

    +

    Experiment Level Control

    Platform Info
    This feature is not supported in the Web platform.

    -

    - Downloading -

    +

    Downloading

    You can fetch information about the A/B tests in your server including test name, description and the current variant:

    -
    Countly.instance.remoteConfig.testingDownloadExperimentInformation((rResult, error){
    +
    Countly.instance.remoteConfig.testingDownloadExperimentInformation((rResult, error){
       // do sth
    -})
    +})

    You can provide a callback (which is optional) to be called when the fetching process ends. Depending on the situation, this would return a RequestResponse Enum (Success, NetworkIssue, or Error) as the first parameter and a String error as the second parameter if there was an error ("null" otherwise).

    -

    - Accessing -

    +

    Accessing

    After fetching the experiment information the SDK saves it in the RAM, so if the memory is erased, you must fetch the information again. You can access this information through this call:

    -
    Countly.sharedInstance().remoteConfig().testingGetAllExperimentInfo()
    +
    Countly.sharedInstance().remoteConfig().testingGetAllExperimentInfo()

    This would return a Future<Map<String, ExperimentInformation>> where the keys are experiment IDs as String and the values are the ExperimentInformation Class which contains information about the experiment with that ID. This Class' structure is like this:

    -
    class ExperimentInformation {
    +
    class ExperimentInformation {
       // same ID as used in the map
       String experimentID;
       // the name of the experiment
    @@ -2398,7 +2408,7 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking();
    -
    {
    +
    {
       some_exp_ID: {
         experimentID: some_ID,
         experimentName: some_name,
    @@ -2417,19 +2427,17 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking();
    -

    - Enrolling / Exiting -

    +

    Enrolling / Exiting

    To enroll a user into the A/B experiment using experiment ID, you use the following method:

    -
    Countly.instance.remoteConfig.testingEnrollIntoABExperiment(String expID);
    +
    Countly.instance.remoteConfig.testingEnrollIntoABExperiment(String expID);

    If you want to remove users from A/B experiment using experiment ID, you can use the following function:

    -
    Countly.instance.remoteConfig.testingExitABExperiment(String expID);
    +
    Countly.instance.remoteConfig.testingExitABExperiment(String expID);

    Extended Device ID Management

    @@ -2439,7 +2447,7 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking();

    You may configure/change the device ID anytime using:

    -
    Countly.changeDeviceId(DEVICE_ID, ON_SERVER);
    +
    Countly.changeDeviceId(DEVICE_ID, ON_SERVER);

    You may either allow the device to be counted as a new device or merge existing data on the server. If theonServer bool is set to @@ -2456,13 +2464,13 @@ config.experimental.enablePreviousNameRecording().enableVisibilityTracking(); You can enable temporary device ID when initializing the SDK:

    -
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
    +
    CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
     config.setDeviceId(Countly.deviceIDType["TemporaryDeviceID"]);
     
     // Initialize with that configuration
     Countly.initWithConfig(config);

    To enable a temporary device ID after init, you would call:

    -
    Countly.changeDeviceId(Countly.deviceIDType["TemporaryDeviceID"], ON_SERVER);
    +
    Countly.changeDeviceId(Countly.deviceIDType["TemporaryDeviceID"], ON_SERVER);

    Note: When passing TemporaryDeviceID for deviceID parameter, argument for onServerparameter @@ -2493,4 +2501,4 @@ Countly.initWithConfig(config);

    What Platforms are supported?

    Currently our Flutter SDK supports Android, iOS, and Web platforms. -

    +

    \ No newline at end of file From 559d45136693aad71b546bc736792f1be413634f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Dec 2025 13:47:36 +0300 Subject: [PATCH 6/8] feat: migrate RN to new editor --- react_native_bridged/current.md | 571 ++++++++++++++++++-------------- react_native_bridged/next.md | 571 ++++++++++++++++++-------------- 2 files changed, 646 insertions(+), 496 deletions(-) diff --git a/react_native_bridged/current.md b/react_native_bridged/current.md index 8da774d7..41bcc368 100644 --- a/react_native_bridged/current.md +++ b/react_native_bridged/current.md @@ -41,7 +41,7 @@ Run the following snippet in the root directory of your React Native project to install the npm dependencies and link native libraries.

    -
    # Include the Countly Class in the file that you want to use.
    +
    # Include the Countly Class in the file that you want to use.
     npm install --save https://github.com/Countly/countly-sdk-react-native-bridge.git
     
     # OR
    @@ -61,12 +61,12 @@ cd ..
    and should be done as early as possible. Your main app component's componentDidMountmethod may be a good place.

    -
    import Countly from 'countly-sdk-react-native-bridge';
    +
    import Countly from 'countly-sdk-react-native-bridge';
     import CountlyConfig from 'countly-sdk-react-native-bridge/CountlyConfig';
     
     if(!await Countly.isInitialized()) {
       // create Countly config object
    -  const countlyConfig = new CountlyConfig("", "YOUR_APP_KEY");
    +  const countlyConfig = new CountlyConfig("", "YOUR_APP_KEY");
       await Countly.initWithConfig(countlyConfig); // Initialize the Countly SDK with config.
     }

    @@ -97,7 +97,7 @@ if(!await Countly.isInitialized()) { Call setLoggingEnabled on the config object to enable logging:

    -
    // create Countly config object
    +
    // create Countly config object
     const countlyConfig = new CountlyConfig("https://your.server.ly", "YOUR_APP_KEY");
     // ...
     countlyConfig.setLoggingEnabled(true); // Enable countly internal debugging logs
    @@ -128,7 +128,7 @@ await Countly.initWithConfig(countlyConfig); // Initialize the countly SDK with
       initWithConfig in order to activate automatic
       crash reporting.
     

    -
    // create Countly config object
    +
    // create Countly config object
     const countlyConfig = new CountlyConfig("https://your.server.ly", "YOUR_APP_KEY");
     // ...
     countlyConfig.enableCrashReporting(); // Enable crash reports
    @@ -141,7 +141,7 @@ await Countly.initWithConfig(countlyConfig); // Initialize the countly SDK with
       segment and the crash reports.
     

    Use the following function for this purpose:

    -
    var segment = {"Key": "Value"};
    +
    var segment = {"Key": "Value"};
     Countly.setCustomCrashSegments(segment);

    Handled Exceptions

    @@ -151,7 +151,7 @@ Countly.setCustomCrashSegments(segment);

    You may also log these handled exceptions to monitor how and when they are happening with the following command:

    -
    Countly.logException(stack, nonfatal, customSegments);
    +
    Countly.logException(stack, nonfatal, customSegments);

    The method logExceptiontakes a string for the stack trace, a boolean flag indicating if the crash is considered fatal or not, @@ -164,19 +164,19 @@ Countly.setCustomCrashSegments(segment);

    1. Manually report handled exception

    -
    Countly.logException("STACK_TRACE_STRING", true);
    +
    Countly.logException("STACK_TRACE_STRING", true);

    2. Manually report handled exception with segmentation

    -
    Countly.logException("STACK_TRACE_STRING", true, {"_facebook_version": "0.0.1"});
    +
    Countly.logException("STACK_TRACE_STRING", true, {"_facebook_version": "0.0.1"});

    3. Manually report fatal exception

    -
    Countly.logException("STACK_TRACE_STRING", false);
    +
    Countly.logException("STACK_TRACE_STRING", false);

    4. Manually report fatal exception with segmentation

    -
    Countly.logException("STACK_TRACE_STRING", false, {"_facebook_version": "0.0.1"});
    +
    Countly.logException("STACK_TRACE_STRING", false, {"_facebook_version": "0.0.1"});

    Crash Breadcrumbs

    Throughout your app, you can leave crash breadcrumbs which would describe previous @@ -184,7 +184,7 @@ Countly.setCustomCrashSegments(segment);

    will be sent together with the crash report.

    Following the command adds crash breadcrumb:

    -
    Countly.addCrashLog(String record) 
    +
    Countly.addCrashLog(String record) 

    Native C++ Crash Reporting

    If you have C++ libraries in your React Native Android app, the React Native @@ -192,7 +192,7 @@ Countly.setCustomCrashSegments(segment);

    the sdk-nativedeveloped within our Android SDK. Find more information - here. + here.

    As this feature is optional, you will need to do some changes in your react native @@ -206,7 +206,7 @@ Countly.setCustomCrashSegments(segment);

    page, currently 23.8.0):

    -
    dependencies {
    +
    dependencies {
       implementation 'ly.count.android:sdk-native:LATEST_VERSION'
     }

    @@ -221,7 +221,7 @@ Countly.setCustomCrashSegments(segment);

    YOUR_REACT_NATIVE_PROJECT_PATH/android/app/src/main/java/com/PROJECT_NAME

    -
    // import this in your Application class
    +
    // import this in your Application class
     import ly.count.android.sdknative.CountlyNative;
     
     // call this function in "onCreate" callback of Application class
    @@ -241,7 +241,7 @@ CountlyNative.initNative(getApplicationContext());

    This is what the debug logs will look like if you use this feature:

    -
    $ adb logcat -s Countly:V countly_breakpad_cpp:V
    +
    $ adb logcat -s Countly:V countly_breakpad_cpp:V
     
     # when Countly.initNative() is called
     
    @@ -272,25 +272,27 @@ D/Countly (124): Recording native crash dump: [30f6d9b8-b3b2-1553-2efe0ba2-36588
       Here are the details about the properties which you can send with an event:
     

      -
    • +
    • eventName name of the event at server (String) (mandatory)
    • -
    • +
    • segmentation key-value pairs that can be used to track additional information (Object)
    • -
    • +
    • eventCount number of times this event occurred. Total count can be viewed at server (Number)
    • -
    • +
    • eventSum any numerical data tied to an event. Total sum can be viewed at server (Number)
    - Data passed should be in UTF-8 +

    + Data passed should be in UTF-8 +

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

    @@ -301,18 +303,18 @@ D/Countly (124): Recording native crash dump: [30f6d9b8-b3b2-1553-2efe0ba2-36588 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. @@ -321,19 +323,19 @@ D/Countly (124): Recording native crash dump: [30f6d9b8-b3b2-1553-2efe0ba2-36588

      1. Event key and count

      -
      Countly.events.recordEvent("Purchase", undefined, 1);
      +
      Countly.events.recordEvent("Purchase", undefined, 1);

      2. Event key, count, and sum

      -
      Countly.events.recordEvent("Purchase", undefined, 1, 0.99);
      +
      Countly.events.recordEvent("Purchase", undefined, 1, 0.99);

      3. Event key and count with segmentation(s)

      -
      Countly.events.recordEvent("Purchase", { Country: "Germany" }, 1 )
      +
      Countly.events.recordEvent("Purchase", { Country: "Germany" }, 1 )

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

      -
      Countly.events.recordEvent("Purchase", { Country: "Germany" }, 1, 0.99)
      +
      Countly.events.recordEvent("Purchase", { Country: "Germany" }, 1, 0.99)

      Those are only a few examples of what you can do with events. You may extend those examples and use Country, game_level, time_of_day, and any other segmentation @@ -346,16 +348,17 @@ D/Countly (124): Recording native crash dump: [30f6d9b8-b3b2-1553-2efe0ba2-36588 of the event. However the start method only serves as a timer and unless the end method is called no event will be recorded.

      -
      //start a timed event
      +
      //start a timed event
       Countly.events.startEvent("Event name");
       //wait some time
       
       //end the event
      -Countly.events.endEvent("Event name");
      +Countly.events.endEvent("Event name"); +

      You may also provide additional information when ending an event:

      -
      //start a timed event
      +
      //start a timed event
       Countly.events.startEvent("Event name");
       //wait some time
       
      @@ -366,9 +369,11 @@ Countly.events.endEvent("Event name", { Country: "Germany", Age: "21" }, 1, 0.99
         You may cancel the started timed event in case it is not relevant anymore. Also
         restarting the app would cancel a started timed event.
       

      -
      //start a timed event
      +
      //start a timed event
       Countly.events.startEvent("Event name");
      -//wait some time

      // cancels the timed event +//wait some time + +// cancels the timed event Countly.events.cancelEvent("Event name"); // now this will not work @@ -385,12 +390,12 @@ Countly.events.endEvent("Event name");

      View Tracking

      You may track custom views with the following code snippet:

      -
      Countly.recordView("View Name")
      +
      Countly.recordView("View Name")

      While manually tracking views, you may add your custom segmentation to them like this:

      -
      var viewSegmentation = { "Country": "Germany", "Age": "28" };
      +
      var viewSegmentation = { "Country": "Germany", "Age": "28" };
       Countly.recordView("View Name", viewSegmentation);

      To review the resulting data, open the dashboard and go to @@ -409,7 +414,7 @@ Countly.recordView("View Name", viewSegmentation);

      SDK generates a random ID during the first initialization. If you want to override it you can use this config method:

      -
      .setDeviceID("newID");
      +
      .setDeviceID("newID");

      SDK Device ID Logic and Rules

      @@ -417,44 +422,37 @@ Countly.recordView("View Name", viewSegmentation);

      You can get the current device ID of the user with this call:

      -
      let currentDeviceId = await Countly().deviceId.getID();
      +
      let currentDeviceId = await Countly().deviceId.getID();

      - You can use deviceId.getType method which returns a value identifying the the current device ID type. The possible type are: + You can use deviceId.getType method which returns a value identifying + the the current device ID type. The possible type are:

        -
      • - DEVELOPER_SUPPLIED - device ID was supplied by the host developer. -
      • -
      • - SDK_GENERATED - device ID was generated by the SDK. -
      • -
      • - TEMPORARY_ID - the SDK is in temporary device ID mode. +
      • + DEVELOPER_SUPPLIED - device ID was supplied by the host developer.
      • +
      • SDK_GENERATED - device ID was generated by the SDK.
      • +
      • TEMPORARY_ID - the SDK is in temporary device ID mode.

      Changing Device ID

      To change the user's current device ID you can use this method:

      -
      Countly().deviceId.setID("newID");
      +
      Countly().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:

        -
      • -

        - If current stored ID is 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 SDK_GENERATED ID will be erased. -

        +
      • + If current stored ID is 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 SDK_GENERATED ID will be erased.
      • -
      • -

        - If the current stored ID is DEVELOPER_SUPPLIED or TEMPORARY_ID 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 DEVELOPER_SUPPLIED or TEMPORARY_ID 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 then you will need to use this method mentioned here instead. + If you need a more complicated logic then you will need to use this method mentioned here instead.

      Temporary Device ID

      @@ -465,7 +463,7 @@ Countly.recordView("View Name", viewSegmentation);

      To enable this when initializing the SDK, use the method below.

      -
      // create Countly config object
      +
      // create Countly config object
       const countlyConfig = new CountlyConfig("https://your.server.ly", "YOUR_APP_KEY");
       //...
       countlyConfig.setDeviceId(Countly.TemporaryDeviceIDString); // Set temporary device ID
      @@ -475,12 +473,12 @@ await Countly.initWithConfig(countlyConfig); // Initialize the countly SDK with
         To enable a temporary device ID after initialization, use the
         method below.
       

      -
      Countly.deviceId.setID(Countly.TemporaryDeviceIDString);
      +
      Countly.deviceId.setID(Countly.TemporaryDeviceIDString);

      As long as the device ID value is - Countly.TemporaryDeviceIDString, - the SDK will be in temporary device ID mode and all requests will be on hold, - but they will be persistently stored. + Countly.TemporaryDeviceIDString, the SDK will + be in temporary device ID mode and all requests will be on hold, but they will + be persistently stored.

      When in temporary device ID mode, method calls for presenting feedback widgets @@ -524,7 +522,7 @@ await Countly.initWithConfig(countlyConfig); // Initialize the countly SDK with Android

    -
    // create Countly config object
    +    
    // create Countly config object
     const countlyConfig = new CountlyConfig("https://your.server.ly", "YOUR_APP_KEY");
     // Set the token types: DEVELOPMENT, PRODUCTION or ADHOC
     countlyConfig.setPushTokenType(Countly.messagingMode.DEVELOPMENT);
    @@ -533,7 +531,7 @@ await Countly.initWithConfig(countlyConfig);
         

    Record Swift Error

    @@ -324,10 +345,11 @@ Countly.sharedInstance().recordException(myException, isFatal: true, stackTrace: Objective-CSwift
    -
    [Countly.sharedInstance recordError:@"ERROR_NAME" stackTrace:[NSThread callStackSymbols]];
    +
    [Countly.sharedInstance recordError:@"ERROR_NAME" stackTrace:[NSThread callStackSymbols]];

    @@ -338,18 +360,22 @@ Countly.sharedInstance().recordException(myException, isFatal: true, stackTrace: Objective-CSwift

    -
    NSDictionary* segmentation = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};

    [Countly.sharedInstance recordError:@"ERROR_NAME" isFatal:YES stackTrace:[NSThread callStackSymbols] segmentation:segmentation];
    +
    NSDictionary* segmentation = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
    +
    +[Countly.sharedInstance recordError:@"ERROR_NAME" isFatal:YES stackTrace:[NSThread callStackSymbols] segmentation:segmentation];

    Crash Breadcrumbs

    - You can use the recordCrashLog: - method to receive custom logs with the crash reports. Logs generated by the recordCrashLog:method are stored in a non-persistent structure and are delivered to the Countly Server only for a crash. + You can use the recordCrashLog: + method to receive custom logs with the crash reports. Logs generated by the recordCrashLog:method are stored in a non-persistent structure and are delivered to the Countly Server only for a crash.

    @@ -357,10 +383,10 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th Swift
    -
    [Countly.sharedInstance recordCrashLog:@"This is a custom crash log."];
    +
    [Countly.sharedInstance recordCrashLog:@"This is a custom crash log."];

    @@ -370,7 +396,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th

    Crash Report Contents

    A crash report includes the following information:

    Default Crash Report Information

    -
    - Exception Info:
    +
    - Exception Info:
       * Exception Name
       * Exception Description
       * Stack Trace
    @@ -409,7 +435,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th
     

    Custom Crash Segmentation

    - If you would like to use custom crash segmentation, you can set the optional crashSegmentation + If you would like to use custom crash segmentation, you can set the optional crashSegmentation dictionary on the CountlyConfig object.

    @@ -419,10 +445,10 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th Swift
    -
    config.crashSegmentation = @{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
    +
    config.crashSegmentation = @{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};

    Crash Filtering

    @@ -439,7 +465,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th The callback receives a CountlyCrashData object, which contains all the information about the crash that would be sent to the server:

    -
    @interface CountlyCrashData : NSObject
    +
    @interface CountlyCrashData : NSObject
     
     @property (nonatomic, copy, nonnull) NSString *stackTrace;
     @property (nonatomic, copy, nonnull) NSString *name;
    @@ -487,7 +513,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th
         Swift
       
       
    -
    #import "Countly.h"
    +    
    #import "Countly.h"
     
     - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
     {
    @@ -538,7 +564,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th
     }
    -
    config.shouldUsePLCrashReporter = YES;
    +
    config.shouldUsePLCrashReporter = YES;

    @@ -622,14 +649,28 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

    Existence of PLCrashReporter dependency will be checked using - __has_include(<CrashReporter/CrashReporter.h>) preprocessor macro. + __has_include(<CrashReporter/CrashReporter.h>) preprocessor macro.

    Note: Countly-PL.podspecautomatically manages the PLCrashReporter dependencies. However, if you encounter an error related to PLCrashReporter when using CocoaPods, you can resolve it by adding the following to your Podfile:

    -
    post_install do |installer|
    installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
    if target.name == "Countly"
    config.build_settings['OTHER_LDFLAGS'] ||= ['$(inherited)']
    config.build_settings['OTHER_LDFLAGS'] << '-framework "CrashReporter"'

    config.build_settings['LIBRARY_SEARCH_PATHS'] ||= ['$(inherited)']
    config.build_settings['LIBRARY_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
    config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= ['$(inherited)']
    config.build_settings['FRAMEWORK_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
    end
    end
    end
    end
    +
    post_install do |installer|
    + installer.pods_project.targets.each do |target|
    +  target.build_configurations.each do |config|
    +   if target.name == "Countly"
    +       config.build_settings['OTHER_LDFLAGS'] ||= ['$(inherited)']
    +       config.build_settings['OTHER_LDFLAGS'] << '-framework "CrashReporter"'
    +
    +       config.build_settings['LIBRARY_SEARCH_PATHS'] ||= ['$(inherited)']
    +       config.build_settings['LIBRARY_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
    +       config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= ['$(inherited)']
    +       config.build_settings['FRAMEWORK_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
    +     end
    +   end
    + end
    +end

    Note: PLCrashReporter option is available only for iOS apps.

    @@ -644,15 +685,14 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

    1) BSD: - PLCrashReporterSignalHandlerTypeBSD + PLCrashReporterSignalHandlerTypeBSD

    2) Mach: - PLCrashReporterSignalHandlerTypeMach + PLCrashReporterSignalHandlerTypeMach

    - For more information about PLCrashReporter please see: - https://github.com/microsoft/plcrashreporter + For more information about PLCrashReporter please see: https://github.com/microsoft/plcrashreporter

    By default, BSD type will be used. For using Mach type signal handler with PLCrashReporter @@ -665,23 +705,24 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau Swift

    -
    config.shouldUseMachSignalHandler = YES;
    +
    config.shouldUseMachSignalHandler = YES;

    PLCrashReporter Callback Blocks

    There is a - crashOccuredOnPreviousSessionCallback + crashOccuredOnPreviousSessionCallback block to be executed when the app is launched again following a crash which is detected by PLCrashReporter on the previous session. It has an - NSDictionary parameter that - represents crash report object. If shouldUsePLCrashReporter + NSDictionary parameter that + represents crash report object. If + shouldUsePLCrashReporter flag is not set on initial config, this block will never be executed. You can - set it - on the CountlyConfig object: + set it on the CountlyConfig object:

    @@ -689,28 +730,31 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau Swift
    -
    config.crashOccuredOnPreviousSessionCallback = ^(NSDictionary * crashReport)
    +    
    config.crashOccuredOnPreviousSessionCallback = ^(NSDictionary * crashReport)
     {  
       NSLog(@"crash report: %@", crashReport);
     };

    There is also another - shouldSendCrashReportCallback + shouldSendCrashReportCallback block to be executed to decide whether the crash report detected by PLCrashReporter on the previous session should be sent to Countly Server or not. If not set, crash report will be sent to Countly Server by default. If set, crash report will be sent to Countly Server only if - YES is returned. It has an - NSDictionary parameter that - represents crash report object. If shouldUsePLCrashReporter + YES is returned. It has an + NSDictionary parameter that + represents crash report object. If + shouldUsePLCrashReporter flag is not set on initial config, this block will never be executed. You can - set it - on the CountlyConfig object: + set it on the CountlyConfig object:

    @@ -718,18 +762,25 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau Swift
    -
    config.shouldSendCrashReportCallback = ^(NSDictionary * crashReport)
    +    
    config.shouldSendCrashReportCallback = ^(NSDictionary * crashReport)
     {                                                                                                                              NSLog(@"crash report: %@", crashReport);
       return YES;    //NO;
     };

    Symbolication

    - Enterprise Edition Feature +

    + Enterprise Edition Feature +

    This feature is only available with an Enterprise Edition and built-in @@ -745,7 +796,7 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

    Automatic dSYM Uploading

    - For Automatic dSYM Uploading, you can use the countly_dsym_uploader script in the Countly iOS SDK. + For Automatic dSYM Uploading, you can use the countly_dsym_uploader script in the Countly iOS SDK.

    To do so, go to the Build Phases @@ -756,15 +807,15 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

    Then, add the following snippet:

    -
    COUNTLY_DSYM_UPLOADER=$(/usr/bin/find $SRCROOT -name "countly_dsym_uploader.sh" | head -n 1)
    +
    COUNTLY_DSYM_UPLOADER=$(/usr/bin/find $SRCROOT -name "countly_dsym_uploader.sh" | head -n 1)
     sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"

    Starting from Xcode 15 you would need to add an Input Files entry to the Run Script section like this:

    -
    ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
    +
    ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}

    - This will make sure the symbolication folder is usable for the script. + This will make sure the symbolication folder is usable for the script.

    Next, select the checkboxRun script only when installing. @@ -776,7 +827,7 @@ sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"< Note: Do not forget to replace your server and app key.

    - By default, Xcode will generate dSYM files for the Release build configuration, and the countly_dsym_uploader script will handle the uploading automatically. You can check for the results on the Report Navigator within Xcode. If the dSYM upload has completed successfully, you will see the[Countly] dSYM upload successfully completed.message. + By default, Xcode will generate dSYM files for the Release build configuration, and the countly_dsym_uploader script will handle the uploading automatically. You can check for the results on the Report Navigator within Xcode. If the dSYM upload has completed successfully, you will see the[Countly] dSYM upload successfully completed.message.

    @@ -793,10 +844,10 @@ sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"< If Bitcode is enabled in your project while uploading your app to App Store Connect, Apple re-compiles your app to optimize it for specific devices. When Apple re-compiles your app, a new dSYM file is generated for the new build, and the dSYM file on your machine will not work for symbolication. So, you will need to receive this new dSYM file manually, then upload it to the Countly Server. In order to get the new dSYM file, you can use App Store Connect or Xcode Organizer.

    - Using App Store Connect: 1. Login to App Store Connect. 2. Go to the Activitytab. 3. Select your app's Version and Build 4. Under General Information click on Download dSYM. 5. If the downloaded file does not have any extension, add .zip and unarchive to see its content. + Using App Store Connect: 1. Login to App Store Connect. 2. Go to the Activitytab. 3. Select your app's Version and Build 4. Under General Information click on Download dSYM. 5. If the downloaded file does not have any extension, add .zip and unarchive to see its content.

    - Using Xcode: 1. Open Organizer in Xcode. 2. Go to the Archives tab. 3. Select your app from the list on the left and select the archive. 4. Click on Download dSYMs.... 5. Xcode inserts the downloaded .dSYM files into the selected archive. + Using Xcode: 1. Open Organizer in Xcode. 2. Go to the Archives tab. 3. Select your app from the list on the left and select the archive. 4. Click on Download dSYMs.... 5. Xcode inserts the downloaded .dSYM files into the selected archive.

    For more information regarding downloading dSYM files from Apple, please see Apple's documentation here. @@ -806,18 +857,18 @@ sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"<

    How to Use Symbolication

    - Once your dSYM file has been uploaded to the Countly Server, you can symbolicate your crash reports coming from that build on the Crashespanel of your Countly Server. + Once your dSYM file has been uploaded to the Countly Server, you can symbolicate your crash reports coming from that build on the Crashespanel of your Countly Server.

    A crash report symbolicated stack trace appears as follows:

    Before symbolication:

    -
    YourAppName                               0x000000010006e174 YourAppName + 156020
    +
    YourAppName                               0x000000010006e174 YourAppName + 156020
     YourAppName                               0x000000010006d060 YourAppName + 151648
     YourAppName                               0x000000010006ad34 YourAppName + 142644
     

    After symbolication:

    -
    -[MHViewController countlyProductionTest] (in YourAppName) (MHViewController.m:620)
    +
    -[MHViewController countlyProductionTest] (in YourAppName) (MHViewController.m:620)
     -[MHViewController transitionToMahya] (in YourAppName) (MHViewController.m:443)
     -[MHViewController textFieldShouldReturn:] (in YourAppName) (MHViewController.m:210)

    @@ -832,7 +883,7 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 We have recorded an event named purchase with different scenarios in the examples below:

      -
    • +
    • purchase event occurred 1 time
    @@ -842,14 +893,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
    -
    [Countly.sharedInstance recordEvent:@"purchase"];
    +
    [Countly.sharedInstance recordEvent:@"purchase"];
      -
    • +
    • purchase event occurred 3 times
    @@ -859,14 +910,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
    -
    [Countly.sharedInstance recordEvent:@"purchase" count:3];
    +
    [Countly.sharedInstance recordEvent:@"purchase" count:3];
      -
    • +
    • purchase event occurred 1 times with the total amount of 3.33
    • @@ -877,14 +928,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
      -
      [Countly.sharedInstance recordEvent:@"purchase" sum:3.33];
      +
      [Countly.sharedInstance recordEvent:@"purchase" sum:3.33];
        -
      • +
      • purchase event occurred 3 times with the total amount of 9.99
      • @@ -895,14 +946,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
        -
        [Countly.sharedInstance recordEvent:@"purchase" count:3 sum:9.99];
        +
        [Countly.sharedInstance recordEvent:@"purchase" count:3 sum:9.99];
          -
        • +
        • purchase event occurred 1 time from country : Germany, on app_version : 1.0 @@ -914,18 +965,18 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
          -
          NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
          +    
          NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
           
           [Countly.sharedInstance recordEvent:@"purchase" segmentation:dict];
            -
          • +
          • purchase event occurred 2 times from country : Germany, on app_version : 1.0 @@ -937,18 +988,18 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict)
    Swift
    -
    NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
    +    
    NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
     
     [Countly.sharedInstance recordEvent:@"purchase" segmentation:dict count:2];
      -
    • +
    • purchase event occurred 2 times with the total amount of 6.66, from country: Germany, on app_version : @@ -961,12 +1012,12 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2)Swift
      -
      NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
      +    
      NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
       
       [Countly.sharedInstance recordEvent:@"purchase" segmentation:dict count:2 sum:6.66];
      @@ -976,7 +1027,7 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum In the examples below, we recorded a timed event called level24 to track how long it takes to complete:

        -
      • +
      • level24 started
      @@ -986,14 +1037,14 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum Swift
      -
      [Countly.sharedInstance startEvent:@"level24"];
      +
      [Countly.sharedInstance startEvent:@"level24"];
        -
      • +
      • level24 ended
      @@ -1003,17 +1054,17 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum Swift
      -
      [Countly.sharedInstance endEvent:@"level24"];
      +
      [Countly.sharedInstance endEvent:@"level24"];

      Additionally, you can provide more information, such as the segmentation, count, and sum while ending an event.

        -
      • +
      • level24 ended 1 time with the total point of 34578, from country : Germany, on app_version : @@ -1026,18 +1077,18 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum Swift
        -
        NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
        +    
        NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
         
         [Countly.sharedInstance endEvent:@"level24" segmentation:dict count:1 sum:34578];

        - The duration of the event will be calculated automatically when the endEvent method is called. + The duration of the event will be calculated automatically when the endEvent method is called.

        You can also cancel a started timed event using cancelEvent method: @@ -1048,17 +1099,17 @@ Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:345

        -
        [Countly.sharedInstance cancelEvent:@"level24"];
        +
        [Countly.sharedInstance cancelEvent:@"level24"];

        Or, if you are measuring the duration of an event yourself, you can record it directly as follows:

          -
        • +
        • level24 took 344 seconds to complete:
        @@ -1068,17 +1119,17 @@ Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:345 Swift
        -
        [Countly.sharedInstance recordEvent:@"level24" duration:344];
        +
        [Countly.sharedInstance recordEvent:@"level24" duration:344];

        Additionally, you can provide more information such as the segmentation, count, and sum.

          -
        • +
        • level24 took 344 seconds to complete 2 times with the total point of 34578, from country : Germany, on @@ -1091,18 +1142,20 @@ Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:345 Swift
          -
          NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
          +    
          NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
           
           [Countly.sharedInstance recordEvent:@"level24" segmentation:dict count:2 sum:34578 duration:344];
          - Event Names and Segmentation +

          + Event Names and Segmentation +

          Event names must be non-zero length valid NSString and segmentation must be an NSDictionary which @@ -1113,11 +1166,11 @@ Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum:

          Sessions

          Automatic Session Tracking

          - By default, the Countly iOS SDK tracks sessions automatically and sends the begin_sessionrequest upon initialization, the end_session request when the app goes to the background, and the begin_session request again when the app comes back to the foreground. In addition, the Countly iOS SDK automatically sends a periodical (60 sec by default) update session request while the app is in the foreground. + By default, the Countly iOS SDK tracks sessions automatically and sends the begin_sessionrequest upon initialization, the end_session request when the app goes to the background, and the begin_session request again when the app comes back to the foreground. In addition, the Countly iOS SDK automatically sends a periodical (60 sec by default) update session request while the app is in the foreground.

          Manual Sessions

          - You can set the manualSessionHandling flag on the CountlyConfig object before starting Countly to handle sessions manually. + You can set the manualSessionHandling flag on the CountlyConfig object before starting Countly to handle sessions manually.

          @@ -1125,14 +1178,14 @@ Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum: Swift
          -
          config.manualSessionHandling = YES;
          +
          config.manualSessionHandling = YES;

          - If the manualSessionHandling flag is set, the Countly iOS SDK does not send the previously mentioned requests automatically, meaning you will need to manually call the beginSession, updateSession and endSession methods after you start Countly, depending on your own definition of a session. + If the manualSessionHandling flag is set, the Countly iOS SDK does not send the previously mentioned requests automatically, meaning you will need to manually call the beginSession, updateSession and endSession methods after you start Countly, depending on your own definition of a session.

          @@ -1140,19 +1193,19 @@ Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum: Swift
          -
          [Countly.sharedInstance beginSession];
          +    
          [Countly.sharedInstance beginSession];
           [Countly.sharedInstance updateSession];
           [Countly.sharedInstance endSession];

          Update Session Period

          - You can specify the updateSessionPeriod on the CountlyConfig object before starting Countly. It is used for session updating and periodically sending queued events to the server. If the updateSessionPeriod is not explicitly set, the default setting will be at 60 seconds for iOS, tvOS & macOS, and 20 seconds for watchOS. + You can specify the updateSessionPeriod on the CountlyConfig object before starting Countly. It is used for session updating and periodically sending queued events to the server. If the updateSessionPeriod is not explicitly set, the default setting will be at 60 seconds for iOS, tvOS & macOS, and 20 seconds for watchOS.

          @@ -1160,16 +1213,16 @@ Countly.sharedInstance().endSession()
    Swift
    -
    config.updateSessionPeriod = 300;
    +
    config.updateSessionPeriod = 300;

    View Tracking

    Automatic Views

    - To enable automatic view tracking, you will need to set the enableAutomaticViewTracking flag on the CountlyConfig object before starting Countly. + To enable automatic view tracking, you will need to set the enableAutomaticViewTracking flag on the CountlyConfig object before starting Countly.

    @@ -1177,21 +1230,21 @@ Countly.sharedInstance().endSession()
    Swift
    -
    config.enableAutomaticViewTracking = YES;
    +
    config.enableAutomaticViewTracking = YES;

    - After this step, the Countly iOS SDK will automatically track views by simply intercepting the viewDidAppear: method of the UIViewControllerclass and reporting which view is displayed with the view name and duration. If the view controller's title property is set, it would be reported as the view name. Otherwise, the view name will be the view controller's class name. + After this step, the Countly iOS SDK will automatically track views by simply intercepting the viewDidAppear: method of the UIViewControllerclass and reporting which view is displayed with the view name and duration. If the view controller's title property is set, it would be reported as the view name. Otherwise, the view name will be the view controller's class name.

    Automatic View Exceptions

    Default Exceptions for Automatic View Tracking

    Following system view controllers will be excluded by default from automatic view tracking, as they are not visible to the user but rather structural controllers:

    -
    UINavigationController
    +
    UINavigationController
     UIAlertController
     UIPageViewController
     UITabBarController
    @@ -1232,7 +1285,7 @@ UIKeyCommandDiscoverabilityHUDViewController
     

    Custom Exceptions for Automatic View Tracking

    - In addition to these default exceptions, you can manually set an exclusion list of the view controllers you don't want to track by using the automaticViewTrackingExclusionList array on the CountlyConfig object before starting Countly + In addition to these default exceptions, you can manually set an exclusion list of the view controllers you don't want to track by using the automaticViewTrackingExclusionList array on the CountlyConfig object before starting Countly

    @@ -1240,10 +1293,10 @@ UIKeyCommandDiscoverabilityHUDViewController Swift
    -
    config.automaticViewTrackingExclusionList = @[NSStringFromClass(MyViewController.class), @"MyViewControllerName"];
    +
    config.automaticViewTrackingExclusionList = @[NSStringFromClass(MyViewController.class), @"MyViewControllerName"];

    @@ -1260,13 +1313,13 @@ UIKeyCommandDiscoverabilityHUDViewController Swift

    -
    //Make your view controller to conform CountlyAutoViewTrackingName protocol.
    +    
    //Make your view controller to conform CountlyAutoViewTrackingName protocol.
     @interface MyViewController : UIViewController @end 
     //and implement countlyAutoViewTrackingName method to return custom view name to be used by Auto View Tracking.
     - (NSString *)countlyAutoViewTrackingName { return @"This is overridden custom view name"; }
    -
    [Countly.sharedInstance.views startAutoStoppedView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
    +
    [Countly.sharedInstance.views startAutoStoppedView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

    Regular Views

    Opposed to "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 manually start a view using the startView:method with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName: or stopViewWithID: + can manually start a view using the startView:method with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName: or stopViewWithID:

    @@ -1326,10 +1379,10 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views startView:@"MyView"];
    +
    [Countly.sharedInstance.views startView:@"MyView"];

    @@ -1341,10 +1394,10 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift

    -
    [Countly.sharedInstance.views startView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
    +
    [Countly.sharedInstance.views startView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

    Stopping Views

    @@ -1354,7 +1407,7 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus

    You can stop view tracking by its name using - stopViewWithName: + stopViewWithName:

    @@ -1362,15 +1415,16 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views stopViewWithName:@"MyView"];
    +
    [Countly.sharedInstance.views stopViewWithName:@"MyView"];

    This function allows you to manually stop the tracking of a view identified by - its name.
    You can also specify the custom segmentation key-value pairs while stopping views:
    + its name.
    + You can also specify the custom segmentation key-value pairs while stopping views:

    @@ -1378,15 +1432,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views stopViewWithName:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
    +
    [Countly.sharedInstance.views stopViewWithName:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

    You can also stop view tracking by its unique idetifier using - stopViewWithID: + stopViewWithID:

    @@ -1394,15 +1448,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views stopViewWithID:@"VIEW_ID"];
    +
    [Countly.sharedInstance.views stopViewWithID:@"VIEW_ID"];

    This function allows you to manually stop the tracking of a view identified by - its unique identifier.
    + its unique identifier.
    You can also specify the custom segmentation key-value pairs while stopping views:

    @@ -1411,15 +1465,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views stopViewWithID:@"VIEW_ID" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
    +
    [Countly.sharedInstance.views stopViewWithID:@"VIEW_ID" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

    You can stop all views tracking using - stopAllViews: + stopAllViews:

    @@ -1427,14 +1481,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views stopAllViews:@{@"key": @"value"}];
    +
    [Countly.sharedInstance.views stopAllViews:@{@"key": @"value"}];

    - This function stops the tracking of all views.
    + This function stops the tracking of all views.
    +  

    Pausing and Resuming Views

    @@ -1446,7 +1501,7 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus

    You can pause view tracking by its unique identifier using - pauseViewWithID: + pauseViewWithID:

    @@ -1454,17 +1509,18 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views pauseViewWithID:@"VIEW_ID"];
    +
    [Countly.sharedInstance.views pauseViewWithID:@"VIEW_ID"];

    - This function temporarily pauses the tracking of a view identified by its unique identifier. + This function temporarily pauses the tracking of a view identified by its unique + identifier.

    - You can resume view tracking by its unique identifier using resumeViewWithID: + You can resume view tracking by its unique identifier using resumeViewWithID:

    @@ -1472,21 +1528,20 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    [Countly.sharedInstance.views resumeViewWithID:@"VIEW_ID"];
    +
    [Countly.sharedInstance.views resumeViewWithID:@"VIEW_ID"];

    This function resumes the tracking of a previously paused view identified by its unique identifier.

    -

    - Adding Segmentation to Started Views -

    +

    Adding Segmentation to Started Views

    - You can also add segmentation to already started views using view name or view ID:
    + You can also add segmentation to already started views using view name or view ID:
    +  

    @@ -1494,13 +1549,13 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
    -
    NSString * viewID = [Countly.sharedInstance.views startView:@"VIEW_NAME"];
    +    
    NSString * viewID = [Countly.sharedInstance.views startView:@"VIEW_NAME"];
     [Countly.sharedInstance.views addSegmentationToViewWithID:viewID segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
           
     [Countly.sharedInstance.views addSegmentationToViewWithName:@"VIEW_NAME" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

    Global View Segmentation

    - You can set global segmentation for views by using setGlobalViewSegmentation: + You can set global segmentation for views by using setGlobalViewSegmentation:

    @@ -1516,14 +1571,14 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    [Countly.sharedInstance.views setGlobalViewSegmentation:@{@"key": @"value"}];
    +
    [Countly.sharedInstance.views setGlobalViewSegmentation:@{@"key": @"value"}];

    - You can also update global segmentation values for views by using updateGlobalViewSegmentation: + You can also update global segmentation values for views by using updateGlobalViewSegmentation:

    @@ -1531,18 +1586,18 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    [Countly.sharedInstance.views updateGlobalViewSegmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
    +
    [Countly.sharedInstance.views updateGlobalViewSegmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

    Device ID Management

    - It is a persistently stored random NSUUID string. + It is a persistently stored random NSUUID string.

    - If you would like to use a custom device ID, you can set the deviceID property on the CountlyConfig object. If the deviceID property is not set explicitly, a default + If you would like to use a custom device ID, you can set the deviceID property on the CountlyConfig object. If the deviceID property is not set explicitly, a default device ID will be used depending on the platform.

    @@ -1551,15 +1606,15 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    config.deviceID = @"customDeviceID";  //Optional custom device ID
    +
    config.deviceID = @"customDeviceID";  //Optional custom device ID

    Note: - Once set, the device ID will be persistently stored on the device after the first app launch, and the deviceID property will be ignored on the following app launches, until the app is deleted and re-installed or a resetStoredDeviceID flag is set. For further details, please check the Resetting Stored Device ID section below. + Once set, the device ID will be persistently stored on the device after the first app launch, and the deviceID property will be ignored on the following app launches, until the app is deleted and re-installed or a resetStoredDeviceID flag is set. For further details, please check the Resetting Stored Device ID section below.

    Changing Device ID

    @@ -1570,7 +1625,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    - You can change the device ID on runtime after you start Countly. You can either allow the device to be counted as a new device or merge existing data on the server.

    To set a new device ID based on the current device ID type, use the setID: method. If the current device ID type is CLYDeviceIDTypeCustom, it will be counted as a new device; otherwise, it will merge existing data on the server. With setID:, the SDK will automatically handle whether to merge the device ID or not.
    + You can change the device ID on runtime after you start Countly. You can either allow the device to be counted as a new device or merge existing data on the server.
    +
    + To set a new device ID based on the current device ID type, use the setID: method. If the current device ID type is CLYDeviceIDTypeCustom, it will be counted as a new device; otherwise, it will merge existing data on the server. With setID:, the SDK will automatically handle whether to merge the device ID or not.
    +  

    @@ -1578,17 +1636,20 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    //Automatically handle whether to merge the device ID or not.
    +    
    //Automatically handle whether to merge the device ID or not.
     [Countly.sharedInstance setID:@"new_device_id"];
    - Consent Reset on Device ID Change

    - If device ID is changed again from a developer provided ID and requiresConsent flag was enabled, all previously given consents will be removed. This means that all features will cease to function until new consent has been given again for the new device ID. + Consent Reset on Device ID Change +

    +

    + If device ID is changed again from a developer provided ID and requiresConsent flag was enabled, all previously given consents will be removed. This means that all features will cease to function until new consent has been given again for the new device ID.

    Temporary Device ID

    @@ -1610,10 +1671,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    [config enableTemporaryDeviceIDMode];
    +
    [config enableTemporaryDeviceIDMode];

    @@ -1625,10 +1686,11 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift

    -
    [Countly.sharedInstance enableTemporaryDeviceIDMode];
    +
    [Countly.sharedInstance enableTemporaryDeviceIDMode];
    +

    @@ -1641,7 +1703,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    Later, when the real device ID is set using - setID: method, all requests + setID: method, all requests which have been kept on hold until that point will start with the real device ID:

    @@ -1651,16 +1713,18 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    [Countly.sharedInstance setID:@"new_device_id"];
    +
    [Countly.sharedInstance setID:@"new_device_id"];
    - Consent Reset on Temporary Device ID Mode

    - If the SDK goes into Temporary Device ID mode and requiresConsent flag was enabled, all previously given consents will be removed. Therefore after entering the Temporary Device ID mode, you should reestablish consent again. + Consent Reset on Temporary Device ID Mode +

    +

    + If the SDK goes into Temporary Device ID mode and requiresConsent flag was enabled, all previously given consents will be removed. Therefore after entering the Temporary Device ID mode, you should reestablish consent again.

    Retrieving Current Device ID

    @@ -1673,10 +1737,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    [Countly.sharedInstance deviceID];
    +
    [Countly.sharedInstance deviceID];

    @@ -1693,10 +1757,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift

    -
    [Countly.sharedInstance deviceIDType];
    +
    [Countly.sharedInstance deviceIDType];

    @@ -1710,7 +1774,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    Resetting Stored Device ID

    - In order to handle device ID changes for logged-in and logged-out users, the device ID specified in the CountlyConfig object of the deviceID property (or the default device ID, if not specified) will be persistently stored as well as the device ID passed to the changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method at any time upon the first app launch. By this point, until you delete and re-install the app, the Countly iOS SDK will continue to use the stored device ID and ignore the deviceID property. So, if you set the deviceID property to something different upon future app launches during development, it will have no effect. In this case, you can set the resetStoredDeviceID flag on the CountlyConfig object in order to reset the stored device ID. This will reset the initially stored device ID and the Countly iOS SDK will work as if it is the first app launch. + In order to handle device ID changes for logged-in and logged-out users, the device ID specified in the CountlyConfig object of the deviceID property (or the default device ID, if not specified) will be persistently stored as well as the device ID passed to the changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method at any time upon the first app launch. By this point, until you delete and re-install the app, the Countly iOS SDK will continue to use the stored device ID and ignore the deviceID property. So, if you set the deviceID property to something different upon future app launches during development, it will have no effect. In this case, you can set the resetStoredDeviceID flag on the CountlyConfig object in order to reset the stored device ID. This will reset the initially stored device ID and the Countly iOS SDK will work as if it is the first app launch.

    @@ -1718,14 +1782,14 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    config.resetStoredDeviceID = YES;
    +
    config.resetStoredDeviceID = YES;

    - After you start Countly once with the resetStoredDeviceID flag while developing, you can remove that line. The resetStoredDeviceID flag is not meant for production. It is only for debugging purposes while performing development and not being able to delete and re-install the app. + After you start Countly once with the resetStoredDeviceID flag while developing, you can remove that line. The resetStoredDeviceID flag is not meant for production. It is only for debugging purposes while performing development and not being able to delete and re-install the app.

    Push Notifications

    @@ -1740,10 +1804,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView app and at your Countly server.

    - First, you will need to acquire Push Notification credentials from Apple. (If you don't have them you can check this article to learn how you can do it.) + First, you will need to acquire Push Notification credentials from Apple. (If you don't have them you can check this article to learn how you can do it.)

    - Then you would need to upload these credentials to your Countly server. You can refer to this article for learning how you can do that. + Then you would need to upload these credential to your Countly server. You can refer to this article for learning how you can do that.

    Lastly you will need to integrate and enable the feature in your SDK as explained below. @@ -1760,7 +1824,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    Enabling Push

    - Now, start Countly in the application:didFinishLaunchingWithOptions: method of your app with the following configuration. Do not forget to specify CLYPushNotifications in the features array on the CountlyConfig object. Then you'll need to ask for user's permission for push notifications using the Countly askForNotificationPermission method at any point in the app. The Countly iOS SDK will automatically handle the rest. No need to call any other method for registering when a device token is generated, or a push notification is received. + Now, start Countly in the application:didFinishLaunchingWithOptions: method of your app with the following configuration. Do not forget to specify CLYPushNotifications in the features array on the CountlyConfig object. Then you'll need to ask for user's permission for push notifications using the Countly askForNotificationPermission method at any point in the app. The Countly iOS SDK will automatically handle the rest. No need to call any other method for registering when a device token is generated, or a push notification is received.

    @@ -1768,7 +1832,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    #import "Countly.h"
    +    
    #import "Countly.h"
     
     - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
     {
    @@ -1791,7 +1855,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
     }

    - Note: Ensure you code-sign your application using the explicit Provisioning Profile specific to your app's bundleID with an aps-environment key in it. You can get it from the iOS Provisioning Profiles section of the Apple Developer website. Be advised, wildcard (*) profiles or profiles aps-environment key do not work with APNs, and the device can not receive a push token. + Note: Ensure you code-sign your application using the explicit Provisioning Profile specific to your app's bundleID with an aps-environment key in it. You can get it from the iOS Provisioning Profiles section of the Apple Developer website. Be advised, wildcard (*) profiles or profiles aps-environment key do not work with APNs, and the device can not receive a push token.

    Note: Please make sure you do not set @@ -1841,22 +1905,22 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    Deep links

    - When you send a push notification with custom actions buttons, you can redirect users to any custom page or view in your app by specifying deep links as custom actions button URLs. To do so, you will first need to create a URL scheme (e.g. : myapp://) in your project. + When you send a push notification with custom actions buttons, you can redirect users to any custom page or view in your app by specifying deep links as custom actions button URLs. To do so, you will first need to create a URL scheme (e.g. : myapp://) in your project.

    - To do so, select your app target in Xcode and open the Info tab. Then, open the URL Types section by clicking the horizontal arrow, and click the plus + sign there. + To do so, select your app target in Xcode and open the Info tab. Then, open the URL Types section by clicking the horizontal arrow, and click the plus + sign there.

    - Enter an identifier (preferably in reverse domain format) into the Identifier field and enter your app's URL scheme (without ://part) into the URL Schemes field. Optionally, you can set an Icon. You can leave the Role field as whatever its default value is. When you are done, you can confirm that your new URL scheme has been added to your app's Info.plist file. It should look like this: + Enter an identifier (preferably in reverse domain format) into the Identifier field and enter your app's URL scheme (without ://part) into the URL Schemes field. Optionally, you can set an Icon. You can leave the Role field as whatever its default value is. When you are done, you can confirm that your new URL scheme has been added to your app's Info.plist file. It should look like this:

    - After setting up the URL scheme, you should add the application:openURL:options: method to your app delegate: + After setting up the URL scheme, you should add the application:openURL:options: method to your app delegate:

    @@ -1864,7 +1928,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
    +    
    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
     {
       //handle URL here to navigate to custom views
     
    @@ -1872,7 +1936,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
     }

    - If your app's deployment target is lower than iOS9, you should add the application:openURL:sourceApplication:annotation: method instead: + If your app's deployment target is lower than iOS9, you should add the application:openURL:sourceApplication:annotation: method instead:

    @@ -1889,7 +1953,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
    +    
    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
     {
       //handle URL here to navigate to custom views
     
    @@ -1897,7 +1961,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
     }

    - Then in this method, you can check the passed url for custom view navigation using thescheme and host properties. For example, if you set the custom action button URLs as countly://productA and countly://productB, you can use something similar to this snippet: + Then in this method, you can check the passed url for custom view navigation using thescheme and host properties. For example, if you set the custom action button URLs as countly://productA and countly://productB, you can use something similar to this snippet:

    @@ -1914,7 +1978,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    if ([url.scheme isEqualToString: @"countly"])
    +    
    if ([url.scheme isEqualToString: @"countly"])
     {
       if ([url.host isEqualToString: @"productA"])
       {
    @@ -1929,7 +1993,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
     }

    - Use the Product Name field of the Notification Service Extension target as you wish (for example: CountlyNSE) and ensure the Team is also selected. + Use the Product Name field of the Notification Service Extension target as you wish (for example: CountlyNSE) and ensure the Team is also selected.

    Note: @@ -1983,7 +2047,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    Note: - If you cannot see the CountlyNotificationService.m file because you are using CocoaPods or Carthage for integration, please locate it yourself (probably under the Pods folder) and add it to your project manually. + If you cannot see the CountlyNotificationService.m file because you are using CocoaPods or Carthage for integration, please locate it yourself (probably under the Pods folder) and add it to your project manually.

    @@ -1991,7 +2055,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

    Then find the NotificationService.m file (NotificationService.swift in Swift projects) - in the extension target. It is a default template file added automatically by Xcode. Import CountlyNotificationService.h inside this file. + in the extension target. It is a default template file added automatically by Xcode. Import CountlyNotificationService.h inside this file.

    @@ -1999,16 +2063,15 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    #import "CountlyNotificationService.h"
    +
    #import "CountlyNotificationService.h"

    - Then add the following line at the end of the - didReceiveNotificationRequest:withContentHandler: method as shown - below: + Then add the following line at the end of the didReceiveNotificationRequest:withContentHandler: + method as shown below:

    @@ -2016,7 +2079,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
    -
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
    +    
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
     {
       self.contentHandler = contentHandler;
       self.bestAttemptContent = [request.content mutableCopy];
    @@ -2027,7 +2090,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
     

    Note: - Please ensure you also configure the App Transport Security setting in the extension's Info.plist file just as with the main application. Otherwise, media attachments from non-https sources cannot be loaded. + Please ensure you also configure the App Transport Security setting in the extension's Info.plist file just as with the main application. Otherwise, media attachments from non-https sources cannot be loaded.

    Note: - Please ensure you check that the Deployment Target version of the extension target is 10, not 10.3 (or whatever minor version Xcode set automatically). Otherwise, users running iOS versions lower than the Deployment Target value will not be able to get rich push notifications. + Please ensure you check that the Deployment Target version of the extension target is 10, not 10.3 (or whatever minor version Xcode set automatically). Otherwise, users running iOS versions lower than the Deployment Target value will not be able to get rich push notifications.

    Provisional Permission for Push Notifications (iOS 12+ only) @@ -2065,7 +2128,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift

    -
    UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionProvisional;
    +    
    UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionProvisional;
     
     [Countly.sharedInstance askForNotificationPermissionWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error)
     {
    @@ -2074,7 +2137,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
     }];

    - If this is the only notification permission type for which you ask, there will be no permission dialog and it will be granted by default. Then, later on, the Notification Center users can swipe on these provisional notifications and cancel the provisional permission anytime they please. The notification permission level then changes to the UNAuthorizationStatusDenied from the UNAuthorizationStatusProvisional state. This functions is a kind of opt-out. + If this is the only notification permission type for which you ask, there will be no permission dialog and it will be granted by default. Then, later on, the Notification Center users can swipe on these provisional notifications and cancel the provisional permission anytime they please. The notification permission level then changes to the UNAuthorizationStatusDenied from the UNAuthorizationStatusProvisional state. This functions is a kind of opt-out.

    How Push Notifications Work in Countly

    When a push notification is received, the Countly iOS SDK handles everything automatically.

    - First, it checks if the notification payload has the Countly specific dictionary (c key) and the notification ID inside it (ikey). If the Countly specific dictionary is present, it processes the notification. Otherwise, it does nothing. In both cases, the Countly iOS SDK forwards the notification to the default application delegate implementation for manual handling. + First, it checks if the notification payload has the Countly specific dictionary (c key) and the notification ID inside it (ikey). If the Countly specific dictionary is present, it processes the notification. Otherwise, it does nothing. In both cases, the Countly iOS SDK forwards the notification to the default application delegate implementation for manual handling.

    The processing of the notification payload depends on the iOS version, the application’s status (background or foreground) at the time of notification reception, and the notification payload's content. @@ -2130,10 +2193,10 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti Swift

    -
    config.pushTestMode = CLYPushTestModeDevelopment;
    +
    config.pushTestMode = CLYPushTestModeDevelopment;

    @@ -2149,10 +2212,10 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti Swift

    -
    config.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
    +
    config.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;

    @@ -2166,7 +2229,7 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti

    Disabling Alerts Shown by Notifications

    - To disable messages from automatically being shown by the CLYPushNotifications feature while the app is in the foreground, you can set the doNotShowAlertForNotifications flag on the CountlyConfig object. If set, no message will be displayed by using the default system UI in the app, but push-open events will be recorded automatically. + To disable messages from automatically being shown by the CLYPushNotifications feature while the app is in the foreground, you can set the doNotShowAlertForNotifications flag on the CountlyConfig object. If set, no message will be displayed by using the default system UI in the app, but push-open events will be recorded automatically.

    @@ -2174,23 +2237,23 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti Swift
    -
    config.doNotShowAlertForNotifications = YES;
    +
    config.doNotShowAlertForNotifications = YES;

    Manually Handling Notifications

    - If you would like to do additional custom work when a push notification is received, all you need to do is implement the default push-related methods in your application delegate (e.g. AppDelegate.m). After finishing its internal work, the Countly iOS SDK will push forward the related method calls to the default implementations on the application delegate. + If you would like to do additional custom work when a push notification is received, all you need to do is implement the default push-related methods in your application delegate (e.g. AppDelegate.m). After finishing its internal work, the Countly iOS SDK will push forward the related method calls to the default implementations on the application delegate.

    - Please ensure you do not set theUNUserNotificationCenter.currentNotificationCenter's delegate manually, as the Countly iOS SDK will be acting as the delegate. All you need to do is directly add theUNUserNotificationCenterDelegate methods to your application delegate class. + Please ensure you do not set theUNUserNotificationCenter.currentNotificationCenter's delegate manually, as the Countly iOS SDK will be acting as the delegate. All you need to do is directly add theUNUserNotificationCenterDelegate methods to your application delegate class.

    - Inside the push notification userInfodictionary you can find all the necessary information under the Countly Payload dictionary specified by the c (kCountlyPNKeyCountlyPayload) key. The array of the custom action buttons is specified by theb (kCountlyPNKeyButtons) key here, and each custom action button's title and action URL is specified by thet (kCountlyPNKeyActionButtonTitle) and l (kCountlyPNKeyActionButtonURL) keys, respectively. Here is an example of the Countly Push Notification Payload: + Inside the push notification userInfodictionary you can find all the necessary information under the Countly Payload dictionary specified by the c (kCountlyPNKeyCountlyPayload) key. The array of the custom action buttons is specified by theb (kCountlyPNKeyButtons) key here, and each custom action button's title and action URL is specified by thet (kCountlyPNKeyActionButtonTitle) and l (kCountlyPNKeyActionButtonURL) keys, respectively. Here is an example of the Countly Push Notification Payload:

    -
    {
    +
    {
       "aps":
       {
         "alert": "this is notification text",
    @@ -2230,7 +2293,7 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti
         Swift
       
       
    -
    NSDictionary* userInfo;     // notification dictionary
    +    
    NSDictionary* userInfo;     // notification dictionary
     NSInteger buttonIndex = 1;  // clicked button index
                                 // 1 for first action button
                                 // 2 for second action button
    @@ -2239,7 +2302,7 @@ NSInteger buttonIndex = 1;  // clicked button index
     

    Always Sending Push Tokens

    - Thanks to iOS’ Remote Notification Background Mode, silent push notifications can be sent to users who have not given notification permission. However, the Countly iOS SDK does not send push tokens to the server by default from users who have not given permission for notifications. You can change this by setting the sendPushTokenAlways flag on the CountlyConfig object. If set, push tokens from all users, regardless of their notification permission status, will be sent to the Countly Server and these users will be listed as possible recipients on the Create Message screen of the Countly Dashboard. Be advised; these users can not be notified by an alert, sound, or badge. This is useful only for sending data via silent notifications. + Thanks to iOS’ Remote Notification Background Mode, silent push notifications can be sent to users who have not given notification permission. However, the Countly iOS SDK does not send push tokens to the server by default from users who have not given permission for notifications. You can change this by setting the sendPushTokenAlways flag on the CountlyConfig object. If set, push tokens from all users, regardless of their notification permission status, will be sent to the Countly Server and these users will be listed as possible recipients on the Create Message screen of the Countly Dashboard. Be advised; these users can not be notified by an alert, sound, or badge. This is useful only for sending data via silent notifications.

    @@ -2258,15 +2321,15 @@ Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonInd Swift
    -
    config.sendPushTokenAlways = YES;
    +
    config.sendPushTokenAlways = YES;

    Notification Permission with Preferred Types and Callback

    - As asking for users’ permission for push notifications differ by iOS versions, the Countly iOS SDK has a one-liner convenience method, askForNotificationPermission, which does this for both iOS10 and older versions. It simply asks for a user's permission for all available notification types. However, if you need to specify which notification types your app will use (alert, badge, sound) or if you need a callback to see a user's response to the permission dialog, you can use theaskForNotificationPermissionWithOptions:completionHandler: + As asking for users’ permission for push notifications differ by iOS versions, the Countly iOS SDK has a one-liner convenience method, askForNotificationPermission, which does this for both iOS10 and older versions. It simply asks for a user's permission for all available notification types. However, if you need to specify which notification types your app will use (alert, badge, sound) or if you need a callback to see a user's response to the permission dialog, you can use theaskForNotificationPermissionWithOptions:completionHandler: method.

    @@ -2275,7 +2338,7 @@ Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonInd Swift
    -
    UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert;
    +    
    UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert;
     
     [Countly.sharedInstance askForNotificationPermissionWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error)
     {
    @@ -2284,7 +2347,7 @@ Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonInd
     }];
    -
    [Countly.sharedInstance disableLocationInfo];
    +
    [Countly.sharedInstance disableLocationInfo];

    @@ -2530,9 +2588,7 @@ config.IP = "255.255.255.255"

    Downloading Values

    -

    - Automatic Remote Config Triggers -

    +

    Automatic Remote Config Triggers

    Automatic remote config triggers have been turned off by default; therefore, no remote config values will be requested without developer intervention.

    @@ -2540,16 +2596,16 @@ config.IP = "255.255.255.255"
    The automatic download triggers that would trigger a full value download are:

      -
    • +
    • when the SDK has finished initializing
    • -
    • +
    • after the device ID is changed without merging
    • -
    • +
    • when user gets out of temp ID mode
    • -
    • +
    • when 'remote-config' consent is given after it had been removed before (if consents are enabled)
    @@ -2564,11 +2620,11 @@ config.IP = "255.255.255.255"
    Swift
    -
    config.enableRemoteConfigAutomaticTriggers = YES;
    +      
    config.enableRemoteConfigAutomaticTriggers = YES;
     
    @@ -2584,11 +2640,11 @@ config.IP = "255.255.255.255"
    Swift
    -
    config.enableRemoteConfigValueCaching = YES;
    +    
    config.enableRemoteConfigValueCaching = YES;
     

    Manual Calls

    @@ -2596,19 +2652,19 @@ config.IP = "255.255.255.255"
    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, omitting (everything except) keys.

    Each of these calls also has an optional parameter that you can provide a RCDownloadCallback to, which would be triggered when the download attempt has finished.

    - downloadKeys is the same as the automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones). + downloadKeys is the same as the automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones).

    @@ -2616,17 +2672,19 @@ config.IP = "255.255.255.255"
    Swift
    -
    [Countly.sharedInstance.remoteConfig downloadKeys:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    //...
    }];
    +
    [Countly.sharedInstance.remoteConfig downloadKeys:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    +   //...
    +}];

    - Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array. + Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array.

    @@ -2634,15 +2692,19 @@ config.IP = "255.255.255.255"
    Swift
    -
    [Countly.sharedInstance.remoteConfig downloadSpecificKeys:NSArray *keys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    //...
    }];
    +
    [Countly.sharedInstance.remoteConfig downloadSpecificKeys:NSArray *keys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    +   //...
    +}];

    - Or you might want to update all the values except a few defined keys. To do so,  call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array. + Or you might want to update all the values except a few defined keys. To do so, call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array.

    @@ -2650,10 +2712,14 @@ config.IP = "255.255.255.255"
    Swift
    -
    [Countly.sharedInstance.remoteConfig downloadOmittingKeys:NSArray *omitKeys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    //...
    }];
    +
    [Countly.sharedInstance.remoteConfig downloadOmittingKeys:NSArray *omitKeys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    +   //...
    +}];
    @@ -2666,8 +2732,7 @@ config.IP = "255.255.255.255"
    key. This returns an CountlyRCData object that contains the value of the key and the metadata about that value's owner. If value in CountlyRCData was null - then no value was found or the value was null. -   + then no value was found or the value was null.

    @@ -2675,10 +2740,18 @@ config.IP = "255.255.255.255"
    Swift
    -
    id value_1 = [Countly.sharedInstance.remoteConfig getValue:@"key_1"].value;
    id value_2 = [Countly.sharedInstance.remoteConfig getValue:@"key_2"].value;
    id value_3 = [Countly.sharedInstance.remoteConfig getValue:@"key_3"].value;
    id value_4 = [Countly.sharedInstance.remoteConfig getValue:@"key_4"].value;

    int intValue = [value_1 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_1 intValue] : 0;
    double doubleValue = [value_2 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_2 doubleValue] : 0.0;
    NSArray *jArray = [value_3 isKindOfClass:[NSArray class]] ? (NSArray*)value_3 : @[];
    NSDictionary *jObj = [value_4 isKindOfClass:[NSDictionary class]] ? (NSDictionary*)value_4 : @{};
    +
    id value_1 = [Countly.sharedInstance.remoteConfig getValue:@"key_1"].value;
    +id value_2 = [Countly.sharedInstance.remoteConfig getValue:@"key_2"].value;
    +id value_3 = [Countly.sharedInstance.remoteConfig getValue:@"key_3"].value;
    +id value_4 = [Countly.sharedInstance.remoteConfig getValue:@"key_4"].value;
    +
    +int intValue = [value_1 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_1 intValue] : 0;
    +double doubleValue = [value_2 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_2 doubleValue] : 0.0;
    +NSArray *jArray = [value_3 isKindOfClass:[NSArray class]] ? (NSArray*)value_3 : @[];
    +NSDictionary *jObj = [value_4 isKindOfClass:[NSDictionary class]] ? (NSDictionary*)value_4 : @{};
    @@ -2772,11 +2851,13 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:] Swift
    -
    [config remoteConfigRegisterGlobalCallback:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    // ...
    }] +
    [config remoteConfigRegisterGlobalCallback:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
    +    // ...
    +}]
     
    @@ -2786,18 +2867,19 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:] and it would have the following parameters:

      -
    • - response: CLYRequestResult Enum (either CLYResponseError, CLYResponseSuccess or CLYResponseNetworkIssue) +
    • + response: CLYRequestResult Enum (either CLYResponseError, + CLYResponseSuccess or CLYResponseNetworkIssue)
    • -
    • +
    • error: NSError (error message. "null" if there is no error)
    • -
    • +
    • fullValueUpdate: BOOL ("true" - all values updated, "false" - a subset of values updated)
    • -
    • +
    • downloadedValues: NSDictionary<NSString *,CountlyRCData*> (the whole downloaded remote config values)
    • @@ -2808,11 +2890,11 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:] Swift
      -
      typedef void (^RCDownloadCallback)(CLYRequestResult response, NSError *_Nullable error, BOOL fullValueUpdate, NSDictionary<NSString*, CountlyRCData *>* downloadedValues);
      +    
      typedef void (^RCDownloadCallback)(CLYRequestResult response, NSError *_Nullable error, BOOL fullValueUpdate, NSDictionary<NSString*, CountlyRCData *>* downloadedValues);
       
      @@ -2832,7 +2914,7 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:] Swift
      -
      // register a callback
      +    
      // register a callback
       [Countly.sharedInstance.remoteConfig registerDownloadCallback:^(CLYRequestResult _Nonnull response, NSError *_Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData*> * _Nonnull downloadedValues) {
          //...
       }];
      @@ -2843,7 +2925,7 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:]
       }];
      -
      config.starRatingCompletion = ^(NSInteger rating)
      +    
      config.starRatingCompletion = ^(NSInteger rating)
       {
         NSLog(@"rating %d",(int)rating);
       };

      - Additionally, you can use the askForStarRating: method to ask for a star rating anytime you would like. It displays the 1 to 5-star rating dialog manually and executes the completion block after the user's action. The completion block takes a single NSInteger parameter that indicates the 1 to 5-star rating given by the user. If the user dismissed the dialog without giving a rating, the value for this rating will be 0, and it will not be reported to the server. Manually asking for a star rating does not affect the automatically requested nature of the star rating. + Additionally, you can use the askForStarRating: method to ask for a star rating anytime you would like. It displays the 1 to 5-star rating dialog manually and executes the completion block after the user's action. The completion block takes a single NSInteger parameter that indicates the 1 to 5-star rating given by the user. If the user dismissed the dialog without giving a rating, the value for this rating will be 0, and it will not be reported to the server. Manually asking for a star rating does not affect the automatically requested nature of the star rating.

      @@ -3027,13 +3107,13 @@ config.starRatingDismissButtonTitle = "No, thanks."
    Swift
    -
    [Countly.sharedInstance askForStarRating:^(NSInteger rating)
    +    
    [Countly.sharedInstance askForStarRating:^(NSInteger rating)
     {
       NSLog(@"rating %li",(long)rating);
     }];

    Feedback Widget

    @@ -3046,10 +3126,10 @@ config.starRatingDismissButtonTitle = "No, thanks."

    It is possible to display 3 kinds of feedback widgets: - NPS, - Survey + NPS, + Survey and - Rating. + Rating. All widgets are shown as webviews and should be approached using the same methods.

    @@ -3072,10 +3152,10 @@ config.starRatingDismissButtonTitle = "No, thanks."

    Swift
    -
    [Countly.sharedInstance feedback];
    +
    [Countly.sharedInstance feedback];

    @@ -3088,7 +3168,7 @@ config.starRatingDismissButtonTitle = "No, thanks."

    Swift
    -
    [presentNPS]
    +    
    [presentNPS]
     [presentSurvey]
     [presentRating]
     // Example:
    @@ -3096,7 +3176,7 @@ config.starRatingDismissButtonTitle = "No, thanks."
    -
    [presentNPS:nameIDorTag
    +        
    [presentNPS:nameIDorTag
     [presentSurvey:nameIDorTag]
     [presentRating:nameIDorTag]
     // Example:
    @@ -3124,7 +3204,7 @@ Countly.sharedInstance().feedback().presentNPS()
             
    -
    [presentNPS:nameIDorTag widgetCallback:]
    +        
    [presentNPS:nameIDorTag widgetCallback:]
     [presentSurvey:nameIDorTag widgetCallback:]
     [presentRating:nameIDorTag widgetCallback:]
     // Example:
    @@ -3160,7 +3240,7 @@ Countly.sharedInstance().feedback().presentNPS("/home-page")
                   
    -
    
    +    
    
     [Countly.sharedInstance getFeedbackWidgets:^(NSArray * feedbackWidgets, NSError * error)
     {
       if (error)
    @@ -3219,7 +3299,7 @@ Countly.sharedInstance().feedback().presentNPS("MyNetPromoterScore") { widgetSta
         
    -
    
    +    
    
     [aFeedbackWidget recordResult:resultDictionary];
     // or
     [aFeedbackWidget recordResult:nil]; // if user dismissed the feedback widget without completing it
         
    -
    //custom properties
    +    
    //custom properties
     Countly.user.custom = @{@"testkey1": @"testvalue1", @"testkey2": @"testvalue2"};
     
     //save
     [Countly.user save];
    -
    [Countly.user set:@"key101" value:@"value101"];
    +    
    [Countly.user set:@"key101" value:@"value101"];
     [Countly.user setOnce:@"key101" value:@"value101"];
     [Countly.user unSet:@"key101"];
     
    @@ -3396,7 +3478,7 @@ Countly.user().save()
    [Countly.user save];
    -
    config.enableOrientationTracking = YES;
    +
    config.enableOrientationTracking = YES;

    Application Performance Monitoring

    @@ -3464,10 +3545,10 @@ Countly.user().save()
    Swift
    -
    config.apm.enableForegroundBackgroundTracking = YES;
    +
    config.apm.enableForegroundBackgroundTracking = YES;

    @@ -3479,8 +3560,7 @@ Countly.user().save()

    Currently iOS SDK support manual app start time tracking. For the app start time to be recorded, you need to set enableAppStartTimeTracking and - enableManualAppLoadedTrigger on the initial - configuration: + enableManualAppLoadedTrigger on the initial configuration:

    @@ -3488,10 +3568,12 @@ Countly.user().save()
    Swift
    -
    config.apm.enableAppStartTimeTracking = YES;
    config.apm.enableManualAppLoadedTrigger = YES;
    +
    config.apm.enableAppStartTimeTracking = YES;
    +config.apm.enableManualAppLoadedTrigger = YES;

    @@ -3512,10 +3594,10 @@ Countly.user().save()

    Swift
    -
    [Countly.sharedInstance appLoadingFinished];
    +
    [Countly.sharedInstance appLoadingFinished];

    @@ -3529,15 +3611,17 @@ Countly.user().save()

    Swift
    -
    long long timestamp = floor(NSDate.date.timeIntervalSince1970 * 1000) - 500;
    [config.apm setAppStartTimestampOverride:timestamp];
    +
    long long timestamp = floor(NSDate.date.timeIntervalSince1970 * 1000) - 500;
    +[config.apm setAppStartTimestampOverride:timestamp];

    Manual Network Traces

    - You can record manual network traces using therecordNetworkTrace:requestPayloadSize:responsePayloadSize:responseStatusCode:startTime:endTime: + You can record manual network traces using therecordNetworkTrace:requestPayloadSize:responsePayloadSize:responseStatusCode:startTime:endTime: method.

    @@ -3563,10 +3647,10 @@ Countly.user().save()

    Swift
    -
    [Countly.sharedInstance recordNetworkTrace:@"/test/endpoint" requestPayloadSize:3445 responsePayloadSize:1290 responseStatusCode:200 startTime:1593418666954 endTime:1593418667384];
    +
    [Countly.sharedInstance recordNetworkTrace:@"/test/endpoint" requestPayloadSize:3445 responsePayloadSize:1290 responseStatusCode:200 startTime:1593418666954 endTime:1593418667384];

    Custom Traces

    @@ -3581,10 +3665,10 @@ Countly.user().save()
    Swift
    -
    [Countly.sharedInstance startCustomTrace:@"unzipping_saved_files"];
    +
    [Countly.sharedInstance startCustomTrace:@"unzipping_saved_files"];

    @@ -3598,10 +3682,10 @@ Countly.user().save()

    Swift
    -
    [Countly.sharedInstance endCustomTrace:@"unzipping_saved_files" metrics:@{@"total_file_size": @1655700}];
    +
    [Countly.sharedInstance endCustomTrace:@"unzipping_saved_files" metrics:@{@"total_file_size": @1655700}];

    @@ -3620,10 +3704,10 @@ Countly.user().save()

    Swift
    -
    [Countly.sharedInstance cancelCustomTrace:@"unzipping_saved_files"];
    +
    [Countly.sharedInstance cancelCustomTrace:@"unzipping_saved_files"];

    @@ -3638,10 +3722,10 @@ Countly.user().save()

    Swift
    -
    [Countly.sharedInstance clearAllCustomTraces];
    +
    [Countly.sharedInstance clearAllCustomTraces];

    @@ -3662,25 +3746,35 @@ Countly.user().save()

    More information about GDPR can be found here.

    -

    - Feature Names -

    +

    Feature Names

    Currently, available features with consent control are as follows:

    -
    CLYConsentSessions
    -CLYConsentEvents
    -CLYConsentUserDetails
    -CLYConsentCrashReporting
    -CLYConsentPushNotifications
    -CLYConsentLocation
    -CLYConsentViewTracking
    -CLYConsentAttribution
    -CLYConsentPerformanceMonitoring
    -CLYConsentFeedback
    -CLYConsentRemoteConfig
    -CLYConsentContent
    -CLYConsentMetrics
    +
    CLYConsentSessions
    +
    +CLYConsentEvents
    +
    +CLYConsentUserDetails
    +
    +CLYConsentCrashReporting
    +
    +CLYConsentPushNotifications
    +
    +CLYConsentLocation
    +
    +CLYConsentViewTracking
    +
    +CLYConsentAttribution
    +
    +CLYConsentPerformanceMonitoring
    +
    +CLYConsentFeedback
    +
    +CLYConsentRemoteConfig
    +
    +CLYConsentContent
    +
    +CLYConsentMetrics

    Setup During Init

    The requirement for consent is disabled by default. To enable it, you will have @@ -3693,14 +3787,15 @@ CLYConsentMetrics

Swift
-
config.requiresConsent = YES;
+
config.requiresConsent = YES;

- With this flag set, the Countly iOS SDK will not automatically collect or send any data and will ignore all manual calls. Until explicit consent is given for a feature, it will remain inactive. After consent for a feature is given, it will launch immediately and will remain active.
You can provide specific consents during initialization
by using the consents array on the CountlyConfig object before starting Countly + With this flag set, the Countly iOS SDK will not automatically collect or send any data and will ignore all manual calls. Until explicit consent is given for a feature, it will remain inactive. After consent for a feature is given, it will launch immediately and will remain active.
+ You can provide specific consents during initialization by using the consents array on the CountlyConfig object before starting Countly

@@ -3708,16 +3803,16 @@ CLYConsentMetrics
Swift
-
config.consents = @[CLYConsentSessions, CLYConsentEvents];
+
config.consents = @[CLYConsentSessions, CLYConsentEvents];

Or, if you would like to give consent for all the features during initialization, you can set - enableAllConsents flag: + enableAllConsents flag:

@@ -3727,21 +3822,20 @@ CLYConsentMetrics
Swift
-
config.enableAllConsents = YES;
+
config.enableAllConsents = YES;
-

- Changing Consent -

+

Changing Consent

- You can also change the consents after initializing the SDK.
To give consent for a feature, you can use the giveConsentForFeature:
method by passing the feature name: + You can also change the consents after initializing the SDK.
+ To give consent for a feature, you can use the giveConsentForFeature:method by passing the feature name:

@@ -3749,11 +3843,11 @@ CLYConsentMetrics
Swift
-
[Countly.sharedInstance giveConsentForFeature:CLYConsentSessions];
+    
[Countly.sharedInstance giveConsentForFeature:CLYConsentSessions];
 [Countly.sharedInstance giveConsentForFeature:CLYConsentEvents];
@@ -3769,10 +3863,10 @@ Countly.sharedInstance().giveConsent(forFeature: CLYConsentEvents) Swift
-
[Countly.sharedInstance giveConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];
+
[Countly.sharedInstance giveConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];

@@ -3785,17 +3879,17 @@ Countly.sharedInstance().giveConsent(forFeature: CLYConsentEvents) Swift

-
[Countly.sharedInstance giveAllConsents];
+
[Countly.sharedInstance giveAllConsents];

The Countly iOS SDK does not persistently store the status of given consents. You are expected to handle receiving consent from end-users using proper UIs depending on your app's context. You are also expected to store them either locally or remotely. Following this step, you will need to call the ‘giving consent’ methods on each app launch, right after starting the Countly iOS SDK depending on the permissions you managed to get from the end-users.

- If the end-user changes his/her mind about consents at a later time, you will need to reflect this in the Countly iOS SDK using the cancelConsentForFeature:method: + If the end-user changes his/her mind about consents at a later time, you will need to reflect this in the Countly iOS SDK using the cancelConsentForFeature:method:

@@ -3803,11 +3897,11 @@ Countly.sharedInstance().giveConsent(forFeature: CLYConsentEvents) Swift
-
[Countly.sharedInstance cancelConsentForFeature:CLYConsentSessions];
+    
[Countly.sharedInstance cancelConsentForFeature:CLYConsentSessions];
 [Countly.sharedInstance cancelConsentForFeature:CLYConsentEvents];
@@ -3822,14 +3916,14 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
[Countly.sharedInstance cancelConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];
+
[Countly.sharedInstance cancelConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];

- Or, if you would like to cancel consent for all the features, you can use the cancelConsentForAllFeaturesconvenience method: + Or, if you would like to cancel consent for all the features, you can use the cancelConsentForAllFeaturesconvenience method:

@@ -3837,10 +3931,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
[Countly.sharedInstance cancelConsentForAllFeatures];
+
[Countly.sharedInstance cancelConsentForAllFeatures];

@@ -3851,11 +3945,11 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)

Security and Privacy

- You can specify extra security features on the CountlyConfig object: + You can specify extra security features on the CountlyConfig object:

Parameter Tamper Protection

- You can set the optional secretSalt to be used for calculating the checksum of the request data which will be sent with each request using the &checksum256 field. You will need to set the exact same secretSalton the Countly Server. If the secretSalton the Countly Server is set, all requests would be checked for validity of the &checksum256field before being processed. + You can set the optional secretSalt to be used for calculating the checksum of the request data which will be sent with each request using the &checksum256 field. You will need to set the exact same secretSalton the Countly Server. If the secretSalton the Countly Server is set, all requests would be checked for validity of the &checksum256field before being processed.

@@ -3863,15 +3957,15 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.secretSalt = @"mysecretsalt";
+
config.secretSalt = @"mysecretsalt";

SSL Certificate Pinning

- You can use optional pinnedCertificates on the CountlyConfig object for specifying bundled certificates to be used for public key pinning. Certificates from your Countly Server must be DER encoded and should have a .der, .cer or .crt extension. They must also be added to your project and be included in the Copy Bundles Resources. + You can use optional pinnedCertificates on the CountlyConfig object for specifying bundled certificates to be used for public key pinning. Certificates from your Countly Server must be DER encoded and should have a .der, .cer or .crt extension. They must also be added to your project and be included in the Copy Bundles Resources.

@@ -3879,15 +3973,15 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.pinnedCertificates = @[@"mycertificate.cer"];
+
config.pinnedCertificates = @[@"mycertificate.cer"];

Using a Self Signed-Server Certificate

- You can set the shouldIgnoreTrustCheck flag on theCountlyConfig object before starting Countly. + You can set the shouldIgnoreTrustCheck flag on theCountlyConfig object before starting Countly.

This flag is used for ignoring all SSL trust check for pinned certificates by @@ -3903,10 +3997,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift

-
config.shouldIgnoreTrustCheck = YES;
+
config.shouldIgnoreTrustCheck = YES;

Other Features and Notes

@@ -3923,10 +4017,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[Countly.sharedInstance setNewAppKey:@"NewAppKey"];
+
[Countly.sharedInstance setNewAppKey:@"NewAppKey"];

@@ -3949,10 +4043,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift

-
[Countly.sharedInstance replaceAllAppKeysInQueueWithCurrentAppKey];
+
[Countly.sharedInstance replaceAllAppKeysInQueueWithCurrentAppKey];

@@ -3971,10 +4065,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift

-
[Countly.sharedInstance removeDifferentAppKeysFromQueue];
+
[Countly.sharedInstance removeDifferentAppKeysFromQueue];

Host

@@ -3989,10 +4083,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[Countly.sharedInstance setNewHost:@"https://example.com"];
+
[Countly.sharedInstance setNewHost:@"https://example.com"];

@@ -4003,7 +4097,7 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)

- You can further specify your optional settings on the CountlyConfig: + You can further specify your optional settings on the CountlyConfig:

Example Integrations

@@ -4032,7 +4126,7 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)

Event Send Threshold

- You can specify the eventSendThreshold on the CountlyConfig object before starting Countly. It is used to send events requests to the server when the number of recorded events reaches the threshold without waiting for the next update session request. If the eventSendThreshold is not explicitly set, the default setting will be at 10 for iOS, tvOS & macOS, and 3 for watchOS. + You can specify the eventSendThreshold on the CountlyConfig object before starting Countly. It is used to send events requests to the server when the number of recorded events reaches the threshold without waiting for the next update session request. If the eventSendThreshold is not explicitly set, the default setting will be at 10 for iOS, tvOS & macOS, and 3 for watchOS.

@@ -4040,10 +4134,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.eventSendThreshold = 5;
+
config.eventSendThreshold = 5;

Setting Maximum Request Queue Size

@@ -4066,15 +4160,15 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
config.storedRequestsLimit = 5000;
+
config.storedRequestsLimit = 5000;

Always using the POST method

- You can set the alwaysUsePOST flag on theCountlyConfig object before starting Countly. This flag is used for sending all requests using the HTTP POST method, regardless of their data size. If set, all requests will be sent using the HTTP POST method. Otherwise, only the requests with a file upload or data size of more than 2,048 bytes will be sent using the HTTP POST method. + You can set the alwaysUsePOST flag on theCountlyConfig object before starting Countly. This flag is used for sending all requests using the HTTP POST method, regardless of their data size. If set, all requests will be sent using the HTTP POST method. Otherwise, only the requests with a file upload or data size of more than 2,048 bytes will be sent using the HTTP POST method.

@@ -4082,10 +4176,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.alwaysUsePOST = YES;
+
config.alwaysUsePOST = YES;

Request Timeout Duration

@@ -4099,21 +4193,24 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
config.requestTimeoutDuration = 60;
+
config.requestTimeoutDuration = 60;

Custom URLSessionConfiguration

For additional networking settings, you can optionally set a custom - URLSessionConfiguration - on the CountlyConfig object, + URLSessionConfiguration + on the CountlyConfig object, to be used with all requests sent to Countly Server.

- If URLSessionConfiguration is not set, NSURLSessionConfiguration's defaultSessionConfiguration will be used by default. + If URLSessionConfiguration is not + set, + NSURLSessionConfiguration's defaultSessionConfiguration + will be used by default.

@@ -4121,10 +4218,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.URLSessionConfiguration = NSURLSessionConfiguration.ephemeralSessionConfiguration;
+
config.URLSessionConfiguration = NSURLSessionConfiguration.ephemeralSessionConfiguration;

@@ -4137,25 +4234,23 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift

-
[Countly.sharedInstance setNewURLSessionConfiguration:newURLSessionConfiguration];
+
[Countly.sharedInstance setNewURLSessionConfiguration:newURLSessionConfiguration];

Custom Metrics

For overriding default metrics or adding extra ones that are sent with - begin_session - requests, you can use - customMetricsdictionary on the CountlyConfig object. + begin_session requests, you + can use customMetricsdictionary on the CountlyConfig object.

Custom metrics should be an - NSDictionary, - with keys and values are both - NSString 's - only. + NSDictionary, with keys and + values are both NSString + 's only.

@@ -4163,18 +4258,27 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.customMetrics = @{@"key": @"value"};
+
config.customMetrics = @{@"key": @"value"};

- For overriding default metrics, keys should be - one of the CLYMetricKey 's: + For overriding default metrics, keys should be one of the + CLYMetricKey 's:

-
CLYMetricKeyDevice
CLYMetricKeyOS
CLYMetricKeyOSVersion
CLYMetricKeyAppVersion
CLYMetricKeyCarrier
CLYMetricKeyResolution
CLYMetricKeyDensity
CLYMetricKeyLocale
CLYMetricKeyHasWatch
CLYMetricKeyInstalledWatchApp
+
CLYMetricKeyDevice
+CLYMetricKeyOS
+CLYMetricKeyOSVersion
+CLYMetricKeyAppVersion
+CLYMetricKeyCarrier
+CLYMetricKeyResolution
+CLYMetricKeyDensity
+CLYMetricKeyLocale
+CLYMetricKeyHasWatch
+CLYMetricKeyInstalledWatchApp
@@ -4182,10 +4286,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift
-
config.customMetrics = @{CLYMetricKeyAppVersion: @"1.2.3"};
+
config.customMetrics = @{CLYMetricKeyAppVersion: @"1.2.3"};

SDK Internal Limits

@@ -4206,10 +4310,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[config.sdkInternalLimits setMaxKeyLength:150];
+
[config.sdkInternalLimits setMaxKeyLength:150];

Value Size

@@ -4223,10 +4327,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[config.sdkInternalLimits setMaxValueSize:200];
+
[config.sdkInternalLimits setMaxValueSize:200];

Segmentation Values

@@ -4239,10 +4343,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[config.sdkInternalLimits setMaxSegmentationValues:120];
+
[config.sdkInternalLimits setMaxSegmentationValues:120];

Breadcrumb Count

@@ -4256,10 +4360,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[config.sdkInternalLimits setMaxBreadcrumbCount:120];
+
[config.sdkInternalLimits setMaxBreadcrumbCount:120];

Stack Trace Lines Per Thread

@@ -4272,10 +4376,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[config.sdkInternalLimits setMaxStackTraceLinesPerThread:50];
+
[config.sdkInternalLimits setMaxStackTraceLinesPerThread:50];

Stack Trace Line Length

@@ -4288,10 +4392,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
[config.sdkInternalLimits setMaxStackTraceLineLength:300];
+
[config.sdkInternalLimits setMaxStackTraceLineLength:300];

Server Configuration

@@ -4310,10 +4414,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
config.sdkBehaviorSettings = @"json server config";
+
config.sdkBehaviorSettings = @"json server config";

@@ -4327,10 +4431,10 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)Swift

-
config.disableSDKBehaviorSettingsUpdates = YES;
+
config.disableSDKBehaviorSettingsUpdates = YES;

Attribution

@@ -4351,11 +4455,11 @@ Countly.sharedInstance().cancelConsent(forFeature: CLYConsentEvents)
Swift
-
NSString* campaignData = @"{\"keyA\":\"valueA\",\"keyB\":\"valueB\"}";
+    
NSString* campaignData = @"{\"keyA\":\"valueA\",\"keyB\":\"valueB\"}";
 [Countly.sharedInstance recordDirectAttributionWithCampaignType:@"countly" andCampaignData:campaignData];
@@ -4374,10 +4478,10 @@ Countly.sharedInstance().recordDirectAttribution(withCampaignType: "countly", an Swift
-
[Countly.sharedInstance recordIndirectAttribution:@{CLYAttributionKeyADID: @"value", @"key1": @"value1", @"key2": @"value2"}];
+
[Countly.sharedInstance recordIndirectAttribution:@{CLYAttributionKeyADID: @"value", @"key1": @"value1", @"key2": @"value2"}];

@@ -4406,10 +4510,10 @@ Countly.sharedInstance().recordDirectAttribution(withCampaignType: "countly", an Swift

-
[Countly.sharedInstance attemptToSendStoredRequests];
+
[Countly.sharedInstance attemptToSendStoredRequests];

@@ -4426,10 +4530,10 @@ Countly.sharedInstance().recordDirectAttribution(withCampaignType: "countly", an Swift

-
[Countly.sharedInstance flushQueues];
+
[Countly.sharedInstance flushQueues];

Backoff Mechanism

@@ -4444,14 +4548,14 @@ Countly.sharedInstance().recordDirectAttribution(withCampaignType: "countly", an Swift
-
CountlyConfig* config = CountlyConfig.new;
+    
CountlyConfig* config = CountlyConfig.new;
 config.appKey = @"YOUR_APP_KEY";
 config.host = @"https://YOUR_COUNTLY_SERVER";
 config.disableBackoffMechanism = YES;
 [Countly.sharedInstance startWithConfig:config];
-
- (void) sendDirectRequest {
+    
- (void) sendDirectRequest {
   NSMutableDictionary *requestMap = [[NSMutableDictionary alloc] init];
   requestMap[@"city"] = @"Istanbul";
   requestMap[@"country_code"] = @"TR";
@@ -4543,7 +4647,7 @@ Countly.sharedInstance().start(with: config)
}
-
NSMutableDictionary *metricsOverride = [NSMutableDictionary dictionary];
+    
NSMutableDictionary *metricsOverride = [NSMutableDictionary dictionary];
 metricsOverride[@"_app_version"] = @"5.0";
 metricsOverride[@"_os"] = @"CustomOS";
 
@@ -4624,7 +4728,7 @@ metricsOverride[@"_os"] = @"CustomOS";
 [Countly.sharedInstance recordMetrics:nil];
-
#import "Countly.h"
+
#import "Countly.h"

@@ -4688,13 +4792,13 @@ Countly.sharedInstance().recordMetrics(nil) Swift

-
CountlyConfig* config = CountlyConfig.new;
+    
CountlyConfig* config = CountlyConfig.new;
 config.appKey = @"YOUR_APP_KEY";
 config.host = @"https://YOUR_COUNTLY_SERVER";
 [Countly.sharedInstance startWithConfig:config];
-
[Countly.sharedInstance suspend];
+
[Countly.sharedInstance suspend];

@@ -4728,10 +4832,10 @@ Countly.sharedInstance().start(with: config) Swift

-
[Countly.sharedInstance resume];
+
[Countly.sharedInstance resume];

@@ -4745,11 +4849,11 @@ Countly.sharedInstance().start(with: config) By the way, the session concept on watchOS is slightly different than the one on the iOS, as watchOS apps are intended for brief user interaction. So, there are two values you might need to adjust depending on your watch apps’ use cases.

    -
  • - The first value is updateSessionPeriod. Its default value is 20 seconds for watchOS and 60 seconds for iOS. This value determines how often session updating requests will be sent to the server while the app is in use. +
  • + The first value is updateSessionPeriod. Its default value is 20 seconds for watchOS and 60 seconds for iOS. This value determines how often session updating requests will be sent to the server while the app is in use.
  • -
  • - The second value is eventSendThreshold, which is 3 for watchOS and 10 for iOS by default. The Countly iOS SDK waits for the number of recorded unique events to reach this threshold to deliver them to the server until the next session updating kicks in. Considering the fact that Apple Watch is designed to be used for short sessions, these values generally seem appropriate. However, you can change them depending on your watchOS app’s scenario. +
  • + The second value is eventSendThreshold, which is 3 for watchOS and 10 for iOS by default. The Countly iOS SDK waits for the number of recorded unique events to reach this threshold to deliver them to the server until the next session updating kicks in. Considering the fact that Apple Watch is designed to be used for short sessions, these values generally seem appropriate. However, you can change them depending on your watchOS app’s scenario.
@@ -4758,28 +4862,28 @@ Countly.sharedInstance().start(with: config) Swift
-
config.updateSessionPeriod = 15;
+    
config.updateSessionPeriod = 15;
 config.eventSendThreshold = 1;

Automatic Reference Counting (ARC)

- The Countly iOS SDK uses Automatic Reference Counting (ARC). If you are integrating the Countly iOS SDK into a non-ARC project, you should add the-fobjc-arc compiler flag to all Countly iOS SDK implementation (*.m) files found under Target > Build Phases > Compile Sources. + The Countly iOS SDK uses Automatic Reference Counting (ARC). If you are integrating the Countly iOS SDK into a non-ARC project, you should add the-fobjc-arc compiler flag to all Countly iOS SDK implementation (*.m) files found under Target > Build Phases > Compile Sources.

App Transport Security (ATS)

- With App Transport Security introduced in iOS 9, connections to non-HTTPS servers which does not meet some requirements will fail with the following error: Error: Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.". You can see details of the requirements here. If your Countly Server instance does not meet these requirements, you can need to add the NSAppTransportSecurity key into your targets' Info.plist files, with NSAllowsArbitraryLoads or NSExceptionDomains as the value, to communicate with your Countly Server. + With App Transport Security introduced in iOS 9, connections to non-HTTPS servers which does not meet some requirements will fail with the following error: Error: Error Domain=NSURLErrorDomain Code=-1022 "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.". You can see details of the requirements here. If your Countly Server instance does not meet these requirements, you can need to add the NSAppTransportSecurity key into your targets' Info.plist files, with NSAllowsArbitraryLoads or NSExceptionDomains as the value, to communicate with your Countly Server.

Swift Projects

- For using Countly on Swift based projects, please ensure your Bridging Header File is configured properly for each target. Then import the Countly.h file into the Bridging Header file, after which you can seamlessly use the Countly methods in your Swift projects. + For using Countly on Swift based projects, please ensure your Bridging Header File is configured properly for each target. Then import the Countly.h file into the Bridging Header file, after which you can seamlessly use the Countly methods in your Swift projects.

- For Notification Service Extension targets, import CountlyNotificationService.h into the Bridging Header file. + For Notification Service Extension targets, import CountlyNotificationService.h into the Bridging Header file.

You can view more details on how to create a Bridging Header file here. @@ -4790,7 +4894,9 @@ config.eventSendThreshold = 1

CocoaPods

- CocoaPods Support +

+ CocoaPods Support +

While the Countly iOS SDK supports integration via CocoaPods, we can not be able to help you with issues stemming from the CocoaPods itself, especially @@ -4800,7 +4906,7 @@ config.eventSendThreshold = 1

You can integrate the Countly iOS SDK using CocoaPods. For more information, please see the Countly CocoaPods page. Please ensure you have the latest version of CocoaPods and your local spec repo is updated. For Notification Service Extension targets, please ensure your Podfile uses something similar to the following sub specs:

-
target 'MyMainApp' do
+
target 'MyMainApp' do
   platform :ios,'10.0'
   pod 'Countly'
 end
@@ -4820,7 +4926,7 @@ end

You can integrate the Countly iOS SDK using Carthage, just add the following to your project's Cartfile:

-
github "Countly/countly-sdk-ios"
+
github "Countly/countly-sdk-ios"

Swift Package Manager (SPM)

You can integrate the Countly iOS SDK with Swift Package Manager (SPM) using @@ -4852,10 +4958,10 @@ end

Swift
-
[Countly.sharedInstance.content enterContentZone];
+
[Countly.sharedInstance.content enterContentZone];

@@ -4873,10 +4979,10 @@ end Swift

-
config.content.zoneTimerInterval = 60;
+
config.content.zoneTimerInterval = 60;

@@ -4888,10 +4994,10 @@ end Swift

-
[Countly.sharedInstance.content refreshContentZone];
+
[Countly.sharedInstance.content refreshContentZone];

@@ -4904,10 +5010,10 @@ end Swift

-
[Countly.sharedInstance.content exitContentZone];
+
[Countly.sharedInstance.content exitContentZone];

@@ -4920,12 +5026,12 @@ end Swift

-
[config.content setGlobalContentCallback:^(ContentStatus contentStatus, NSDictionary<NSString *,id> * _Nonnull contentData) {
+    
[config.content setGlobalContentCallback:^(ContentStatus contentStatus, NSDictionary<NSString *,id> * _Nonnull contentData) {
       // do sth
     }];
@@ -4941,8 +5047,8 @@ end

This class allows enabling two experimental features:

    -
  • Previous Name Recording
  • -
  • Visibility Tracking
  • +
  • Previous Name Recording
  • +
  • Visibility Tracking

When you enable previous name recording, it will add previous view name to the @@ -4955,10 +5061,10 @@ end Swift

-
config.experimental.enablePreviousNameRecording = YES;
+
config.experimental.enablePreviousNameRecording = YES;

@@ -4972,10 +5078,10 @@ end Swift

-
config.experimental.enableVisibiltyTracking = YES;
+
config.experimental.enableVisibiltyTracking = YES;

A/B Testing Variant Information

@@ -4996,12 +5102,12 @@ end
Swift
-
[Countly.sharedInstance.remoteConfig testingDownloadVariantInformation:^(CLYRequestResult _Nonnull response, NSError *_Nonnull error) {
+    
[Countly.sharedInstance.remoteConfig testingDownloadVariantInformation:^(CLYRequestResult _Nonnull response, NSError *_Nonnull error) {
    // do sth
 }];
@@ -5025,10 +5131,10 @@ end Swift
-
NSDictionary*allVariants = [Countly.sharedInstance.remoteConfig testingGetAllVariants];
+
NSDictionary*allVariants = [Countly.sharedInstance.remoteConfig testingGetAllVariants];
@@ -5037,7 +5143,7 @@ end variants under that parameter. The parameter would be the key, and its value would be a String Array of variants. For example:

-
{
+
{
   "key_1" : ["variant_1", "variant_2"],
   "key_2" : ["variant_3"]
 }
@@ -5049,10 +5155,10 @@ end
Swift
-
NSArray* variants = [Countly.sharedInstance.remoteConfig testingGetVariantsForKey:key];
+
NSArray* variants = [Countly.sharedInstance.remoteConfig testingGetVariantsForKey:key];
@@ -5061,7 +5167,7 @@ end
variants were present for a key, it would return an empty array. A typical result would look like this:

-
["variant_1", "variant_2"]
+
["variant_1", "variant_2"]
 

Enrolling For a Variant

@@ -5075,12 +5181,12 @@ end

Swift
-
[Countly.sharedInstance.remoteConfig testingEnrollIntoVariant:key variantName:variantName completionHandler:^(CLYRequestResult _Nonnull response, NSError *_Nonnull error) {
+    
[Countly.sharedInstance.remoteConfig testingEnrollIntoVariant:key variantName:variantName completionHandler:^(CLYRequestResult _Nonnull response, NSError *_Nonnull error) {
    // do sth
 }];
@@ -5104,10 +5210,10 @@ end Swift
-
config.setRequestDropAgeHours(10);
+
config.setRequestDropAgeHours(10);

@@ -5119,10 +5225,11 @@ end

Extended Device ID Management

- You can use the changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method to change the device ID on runtime after you start Countly. You can either allow the device to be counted as a new device or merge existing data on the server. + You can use the changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method to change the device ID on runtime after you start Countly. You can either allow the device to be counted as a new device or merge existing data on the server.

- With this method changeDeviceIDWithMerge: the old device ID on the server will be replaced with the new one, and data associated with the old device ID will be merged automatically.
With changeDeviceIDWithoutMerge: a new device ID created on the server.
+ With this method changeDeviceIDWithMerge: the old device ID on the server will be replaced with the new one, and data associated with the old device ID will be merged automatically.
+ With changeDeviceIDWithoutMerge: a new device ID created on the server.

@@ -5137,14 +5244,14 @@ end Swift

-
//change and merge on server
+    
//change and merge on server
 [Countly.sharedInstance changeDeviceIDWithMerge:@"new_device_id"];
 
 //no replace and merge on server, device will be counted as new
 [Countly.sharedInstance changeDeviceIDWithoutMerge:@"new_device_id"];
-
config.deviceID = CLYTemporaryDeviceID;
+
config.deviceID = CLYTemporaryDeviceID;

@@ -5181,10 +5288,11 @@ Countly.sharedInstance().changeDeviceIDWithoutMerge("new_device_id")Swift

-
[Countly.sharedInstance changeDeviceIDWithoutMerge:CLYTemporaryDeviceID];
+
[Countly.sharedInstance changeDeviceIDWithoutMerge:CLYTemporaryDeviceID];
+

@@ -5194,7 +5302,7 @@ Countly.sharedInstance().changeDeviceIDWithoutMerge("new_device_id")

Later, when the real device ID is set using - changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: + changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method, all requests which have been kept on hold until that point will start with the real device ID:

@@ -5204,198 +5312,191 @@ Countly.sharedInstance().changeDeviceIDWithoutMerge("new_device_id")
Swift
-
[Countly.sharedInstance changeDeviceIDWithMerge:@"new_device_id"];

[Countly.sharedInstance changeDeviceIDWithoutMerge:@"new_device_id"];
+
[Countly.sharedInstance changeDeviceIDWithMerge:@"new_device_id"];
+
+[Countly.sharedInstance changeDeviceIDWithoutMerge:@"new_device_id"];

Note: When setting real device ID while the current device ID - is CLYTemporaryDeviceID, with merge or without merge does not - matter. + is CLYTemporaryDeviceID, with merge or without merge does not matter.

-

- FAQ -

+

FAQ

This section highlights the most frequently asked questions and any troubleshooting queries you may face while integrating the Countly iOS SDK into your iOS, watchOS, tvOS, or macOS applications.

- What platforms does Countly iOS SDK support? + What platforms does Countly iOS SDK support?

- Even though its official name is Countly iOS SDK, it supports all Apple platforms (macOS, tvOS, and watchOS), in addition to iOS. You can use the same SDK for all kinds of projects with different sets of features available for each platform. You can also see how to integrate it into your projects by checking our sample apps here. + Even though its official name is Countly iOS SDK, it supports all Apple platforms (macOS, tvOS, and watchOS), in addition to iOS. You can use the same SDK for all kinds of projects with different sets of features available for each platform. You can also see how to integrate it into your projects by checking our sample apps here.

- Which features are available for each platform? + Which features are available for each platform?

- In addition to Analytics, Events, and User Profiles features, Countly iOS SDK has Push Notifications, Crash Reporting, Auto View Tracking, Remote Config, and Star-Rating features. Availability of these features for platforms are as follows: + In addition to Analytics, Events, and User Profiles features, Countly iOS SDK has Push Notifications, Crash Reporting, Auto View Tracking, Remote Config, and Star-Rating features. Availability of these features for platforms are as follows:

    -
  • -

    - iOS
    - Analytics, Custom Events, User Profiles, Push Notifications, Crash Reporting, Auto View Tracking, Star-Rating, Remote Config, -

    +
  • + iOS
    + Analytics, Custom Events, User Profiles, Push Notifications, Crash Reporting, Auto View Tracking, Star-Rating, Remote Config,
  • -
  • -

    - macOS
    - Analytics, Custom Events, User Profiles, Push Notifications,Crash Reporting, Remote Config, -

    +
  • + macOS
    + Analytics, Custom Events, User Profiles, Push Notifications,Crash Reporting, Remote Config,
  • -
  • -

    - tvOS
    - Analytics, Custom Events, User Profiles, Auto View Tracking,Crash Reporting, Remote Config, -

    +
  • + tvOS
    + Analytics, Custom Events, User Profiles, Auto View Tracking,Crash Reporting, Remote Config,
  • -
  • -

    - watchOS
    - Analytics, Custom Events, User Profiles, Crash Reporting,Remote Config, -

    +
  • + watchOS
    + Analytics, Custom Events, User Profiles, Crash Reporting,Remote Config,

- Can I integrate Countly iOS SDK using CocoaPods? + Can I integrate Countly iOS SDK using CocoaPods?

- We keep our Countly.podspec file up-to-date, so you can integrate Countly iOS SDK using CocoaPods. But, please make sure you read our notes to avoid issues. + We keep our Countly.podspec file up-to-date, so you can integrate Countly iOS SDK using CocoaPods. But, please make sure you read our notes to avoid issues.

- How can I tell which Countly iOS SDK version I am using? + How can I tell which Countly iOS SDK version I am using?

- You can check for kCountlySDKVersion constant in Countly iOS SDK source. It is defined as NSString* const kCountlySDKVersion = @"18.08"; + You can check for kCountlySDKVersion constant in Countly iOS SDK source. It is defined as NSString* const kCountlySDKVersion = @"18.08";

- What is the difference between Default properties and Custom properties of User Profiles? + What is the difference between Default properties and Custom properties of User Profiles?

- User Profiles (only available in Enterprise Edition) has two kinds of properties: Default properties and Custom properties. + User Profiles (only available in Enterprise Edition) has two kinds of properties: Default properties and Custom properties.

- Default properties are predefined fields like name, username, email, birth year, organization, gender, phone number and profile picture. They are displayed in their own place in User Profiles section. You can set them using default properties on Countly.user singleton ( Ex: Countly.user.email = @"john@doe.com"; ) and record them using [Countly.user save]; method. + Default properties are predefined fields like name, username, email, birth year, organization, gender, phone number and profile picture. They are displayed in their own place in User Profiles section. You can set them using default properties on Countly.user singleton ( Ex: Countly.user.email = @"john@doe.com"; ) and record them using [Countly.user save]; method.

- Custom properties are custom defined key-value pairs. You can set them using Countly.user.custom dictionary ( Ex: Countly.user.custom = @{@"testkey1":@"testvalue1", @"testkey2":@"testvalue2"}; ) and record them using [Countly.user save]; method as well. + Custom properties are custom defined key-value pairs. You can set them using Countly.user.custom dictionary ( Ex: Countly.user.custom = @{@"testkey1":@"testvalue1", @"testkey2":@"testvalue2"}; ) and record them using [Countly.user save]; method as well.

- In addition to this, you can use Custom Property Modifiers to set, unset or modify Custom Properties and record your changes using [Countly.user save]; method again. + In addition to this, you can use Custom Property Modifiers to set, unset or modify Custom Properties and record your changes using [Countly.user save]; method again.

- For details please see User Profiles documentation. + For details please see User Profiles documentation.

- How can I handle logged in and logged out users? + How can I handle logged in and logged out users?

- When a user logs in on your app and you have a uniquely identifiable string for that user (like user ID or email address), you can use it instead of device ID to track all info afterwards, without losing all the data generated by that user so far. You can use changeDeviceIDWithMerge:method ( Ex: [Countly.sharedInstance changeDeviceIDWithMerge:@"user123@example.com"]; ). This will replace previously used device ID on device, and merge all existing data on server. + When a user logs in on your app and you have a uniquely identifiable string for that user (like user ID or email address), you can use it instead of device ID to track all info afterwards, without losing all the data generated by that user so far. You can use changeDeviceIDWithMerge:method ( Ex: [Countly.sharedInstance changeDeviceIDWithMerge:@"user123@example.com"]; ). This will replace previously used device ID on device, and merge all existing data on server.

- Why are events not displayed on Countly Server dashboard? + Why are events not displayed on Countly Server dashboard?

- Events are queued but not sent to server until next updateSessionPeriod (60 seconds by default) or eventSendThreshold (10 by default) is reached. So, a little delay may be expecting in displaying events on Countly Server dashboard, while still seeing session data immediately. + Events are queued but not sent to server until next updateSessionPeriod (60 seconds by default) or eventSendThreshold (10 by default) is reached. So, a little delay may be expecting in displaying events on Countly Server dashboard, while still seeing session data immediately.

- In addition to this, Countly iOS SDK sends previously stored requests, if any, followed by a begin_session request, when it starts. If your app records any events meanwhile, these events will be queued and sent to server when all previously queued requests are successfully completed. + In addition to this, Countly iOS SDK sends previously stored requests, if any, followed by a begin_session request, when it starts. If your app records any events meanwhile, these events will be queued and sent to server when all previously queued requests are successfully completed.

- Is it possible to use Countly iOS SDK with another crash SDK? + Is it possible to use Countly iOS SDK with another crash SDK?

- In iOS there can only be one uncaught exception handler. Even though it is possible to save the previous handler and pass the uncaught exception to the previous handler as well, it is not safe to assume that it will work in all cases. We do not know how other SDKs are implemented or whether iOS will give enough time for the all the handlers to do their work before terminating the app, hence, we advise to use Countly as the only crash handler. + In iOS there can only be one uncaught exception handler. Even though it is possible to save the previous handler and pass the uncaught exception to the previous handler as well, it is not safe to assume that it will work in all cases. We do not know how other SDKs are implemented or whether iOS will give enough time for the all the handlers to do their work before terminating the app, hence, we advise to use Countly as the only crash handler.

- Why are my test crashes not reported? + Why are my test crashes not reported?

- If you are running your app with Xcode debugger attached while forcing a test crash, Countly iOS SDK cannot handle the crash as debugger will be intercepting. Please make sure you run your app without Xcode debugger attached. + If you are running your app with Xcode debugger attached while forcing a test crash, Countly iOS SDK cannot handle the crash as debugger will be intercepting. Please make sure you run your app without Xcode debugger attached.

- How can I manually record push notification custom button actions? + How can I manually record push notification custom button actions?

- If you have set doNotShowAlertForNotifications flag on initial configuration object to handle push notifications manually, you can create your own custom UI to show notification message and action buttons. For this, just implement - (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler method in your application's delegate. For details of handling notification manually, please see Handling Notifications Manually section. + If you have set doNotShowAlertForNotifications flag on initial configuration object to handle push notifications manually, you can create your own custom UI to show notification message and action buttons. For this, just implement - (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler method in your application's delegate. For details of handling notification manually, please see Handling Notifications Manually section.

- How can I get rid of compiler warning "No rule to process file"? + How can I get rid of compiler warning "No rule to process file"?

- If you get Warning: no rule to process file '../countly-sdk-ios/README.md' of type net.daringfireball.markdown for architecture arm64 in Xcode, it means README.md (and/or CHANGELOG.md) file is added to Build Phases > Compile Sources in your target. Please remove README.md from Compile Sources list. + If you get Warning: no rule to process file '../countly-sdk-ios/README.md' of type net.daringfireball.markdown for architecture arm64 in Xcode, it means README.md (and/or CHANGELOG.md) file is added to Build Phases > Compile Sources in your target. Please remove README.md from Compile Sources list.

- How is Countly affected by Apple's App Tracking Transparency changes? + How is Countly affected by Apple's App Tracking Transparency changes?

- As Countly is not and has never been doing any tracking, it is not affected by Apple's App Tracking Transparency changes. + As Countly is not and has never been doing any tracking, it is not affected by Apple's App Tracking Transparency changes.

- Definition of "tracking" by Apple's User Privacy and Data Use guidelines: + Definition of "tracking" by Apple's User Privacy and Data Use guidelines:

- “Tracking” refers to linking data collected from your app about a particular end-user or device, such as a user ID, device ID, or profile, with Third-Party Data for targeted advertising or advertising measurement purposes, or sharing data collected from your app about a particular end-user or device with a data broker. + “Tracking” refers to linking data collected from your app about a particular end-user or device, such as a user ID, device ID, or profile, with Third-Party Data for targeted advertising or advertising measurement purposes, or sharing data collected from your app about a particular end-user or device with a data broker.

- For further information please see App Privact Details section on Apple Developer website. + For further information please see App Privact Details section on Apple Developer website.

- What is the average data size of a Countly iOS SDK request sent to Countly Server? + What is the average data size of a Countly iOS SDK request sent to Countly Server?

- While there are several types of requests that Countly iOS SDK sends to Countly Server, the most common ones are: + While there are several types of requests that Countly iOS SDK sends to Countly Server, the most common ones are:

    -
  • - Begin Session Request: It is sent on every app launch (and session start after coming back from the background), and it includes basic metrics.
    - An example Begin Session request (498 bytes) : +
  • + Begin Session Request: It is sent on every app launch (and session start after coming back from the background), and it includes basic metrics.
    + An example Begin Session request (498 bytes) :
-
http://mycountlyserver.com/i?app_key=0000000000000000000000000000000000000000
+
http://mycountlyserver.com/i?app_key=0000000000000000000000000000000000000000
 &device_id=00000000-0000-0000-0000-000000000000
 &timestamp=1534402860000&hour=16&dow=5&tz=540
 &sdk_version=18.08&sdk_name=objc-native-ios
 &begin_session=1
 &metrics=%7B%22_device%22%3A%22iPhone9%2C1%22%2C%22_os%22%3A%22iOS%22%2C%22_os_version%22%3A%2211.4.1%22%2C%22_locale%22%3A%22en_JP%22%2C%22_density%22%3A%22%402x%22%2C%22_resolution%22%3A%22750x1334%22%2C%22_app_version%22%3A%221.0%22%2C%20%22_carrier%22%3A%22NTT%22%7D
-
+
    -
  • - Update Session Request: It is sent every 60 seconds by default, but it depends on Countly iOS SDK initial configuration.
    - An example Update Session request (233 bytes) : +
  • + Update Session Request: It is sent every 60 seconds by default, but it depends on Countly iOS SDK initial configuration.
    + An example Update Session request (233 bytes) :
-
http://mycountlyserver.com/i?app_key=0000000000000000000000000000000000000000
+
http://mycountlyserver.com/i?app_key=0000000000000000000000000000000000000000
 &device_id=00000000-0000-0000-0000-000000000000
 &timestamp=1534402920000&hour=16&dow=5&tz=540
 &sdk_version=18.08&sdk_name=objc-native-ios
 &session_duration=60
-
+
    -
  • - End Session Request: It is sent at the end of a session, when the app goes to background or terminates.
    - An example End Session request (247 bytes) : +
  • + End Session Request: It is sent at the end of a session, when the app goes to background or terminates.
    + An example End Session request (247 bytes) :
-
http://mycountlyserver.com/i?app_key=0000000000000000000000000000000000000000
+
http://mycountlyserver.com/i?app_key=0000000000000000000000000000000000000000
 &device_id=00000000-0000-0000-0000-000000000000
 &timestamp=1534402956000&hour=16&dow=5&tz=540
 &sdk_version=18.08&sdk_name=objc-native-ios
 &session_duration=36
 &end_session=1
-
+
    -
  • - Other Requests For Events, User Details, Push Notifications, Crash Reporting, View Tracking, Feedbacks, Consents, and some other features: Countly iOS SDK sends various requests with various data sizes. Frequency and size of these requests depend on Countly iOS SDK initial configuration and your app's use cases, as well as the end user. +
  • + Other Requests For Events, User Details, Push Notifications, Crash Reporting, View Tracking, Feedbacks, Consents, and some other features: Countly iOS SDK sends various requests with various data sizes. Frequency and size of these requests depend on Countly iOS SDK initial configuration and your app's use cases, as well as the end user.

- What Information is Collected by the SDK? + What Information is Collected by the SDK?

The following description mentions data that is collected by SDK's to perform @@ -5419,18 +5520,18 @@ Countly.sharedInstance().changeDeviceIDWithoutMerge("new_device_id")

    -
  1. +
  2. You are setting UNUserNotificationCenter.currentNotificationCenter's delegate manually at some point, so Countly iOS SDK can not handle the notification.
  3. -
  4. +
  5. requiresConsent Flag is enabled on initial config, but consent for Push Notifications feature is not granted (Note that this has nothing to do with iOS notification permission).
  6. -
  7. +
  8. Notification is not coming from Countly and it does not have any value for kCountlyPNKeyNotificationID = @"i" key in it
  9. -
+ \ No newline at end of file diff --git a/ios/next.md b/ios/next.md index 26aeda8e..4bdcb1f8 100644 --- a/ios/next.md +++ b/ios/next.md @@ -54,7 +54,7 @@ Swift
-
#import "Countly.h"
+    
#import "Countly.h"
 
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
@@ -70,7 +70,7 @@
 
-
config.loggerDelegate = self; //or any other object to act as CountlyLoggerDelegate
+
config.loggerDelegate = self; //or any other object to act as CountlyLoggerDelegate

@@ -222,7 +238,7 @@ Swift

-
// CountlyLoggerDelegate protocol method
+    
// CountlyLoggerDelegate protocol method
 - (void)internalLog:(NSString *)log
 {
 
@@ -230,7 +246,7 @@
 
-
NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];
+    
NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];
 
 [Countly.sharedInstance recordException:myException];
@@ -286,12 +302,12 @@ Countly.sharedInstance().recordException(myException) Objective-CSwift
-
NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];
+    
NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];
 
 [Countly.sharedInstance recordException:myException isFatal:NO];
@@ -305,13 +321,18 @@ Countly.sharedInstance().recordException(myException, isFatal: false)Swift
-
NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];
-
NSDictionary* segmentation = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};

[Countly.sharedInstance recordException:myException isFatal:YES stackTrace:[NSThread callStackSymbols] segmentation:segmentation];
+
NSException* myException = [NSException exceptionWithName:@"MyException" reason:@"MyReason" userInfo:@{@"key":@"value"}];
+
+NSDictionary* segmentation = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
+
+[Countly.sharedInstance recordException:myException isFatal:YES stackTrace:[NSThread callStackSymbols] segmentation:segmentation];

Record Swift Error

@@ -324,10 +345,11 @@ Countly.sharedInstance().recordException(myException, isFatal: true, stackTrace: Objective-CSwift
-
[Countly.sharedInstance recordError:@"ERROR_NAME" stackTrace:[NSThread callStackSymbols]];
+
[Countly.sharedInstance recordError:@"ERROR_NAME" stackTrace:[NSThread callStackSymbols]];

@@ -338,18 +360,22 @@ Countly.sharedInstance().recordException(myException, isFatal: true, stackTrace: Objective-CSwift

-
NSDictionary* segmentation = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};

[Countly.sharedInstance recordError:@"ERROR_NAME" isFatal:YES stackTrace:[NSThread callStackSymbols] segmentation:segmentation];
+
NSDictionary* segmentation = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
+
+[Countly.sharedInstance recordError:@"ERROR_NAME" isFatal:YES stackTrace:[NSThread callStackSymbols] segmentation:segmentation];

Crash Breadcrumbs

- You can use the recordCrashLog: - method to receive custom logs with the crash reports. Logs generated by the recordCrashLog:method are stored in a non-persistent structure and are delivered to the Countly Server only for a crash. + You can use the recordCrashLog: + method to receive custom logs with the crash reports. Logs generated by the recordCrashLog:method are stored in a non-persistent structure and are delivered to the Countly Server only for a crash.

@@ -357,10 +383,10 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th Swift
-
[Countly.sharedInstance recordCrashLog:@"This is a custom crash log."];
+
[Countly.sharedInstance recordCrashLog:@"This is a custom crash log."];

@@ -370,7 +396,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th

Crash Report Contents

A crash report includes the following information:

Default Crash Report Information

-
- Exception Info:
+
- Exception Info:
   * Exception Name
   * Exception Description
   * Stack Trace
@@ -409,7 +435,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th
 

Custom Crash Segmentation

- If you would like to use custom crash segmentation, you can set the optional crashSegmentation + If you would like to use custom crash segmentation, you can set the optional crashSegmentation dictionary on the CountlyConfig object.

@@ -419,10 +445,10 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th Swift
-
config.crashSegmentation = @{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
+
config.crashSegmentation = @{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};

Crash Filtering

@@ -439,7 +465,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th The callback receives a CountlyCrashData object, which contains all the information about the crash that would be sent to the server:

-
@interface CountlyCrashData : NSObject
+
@interface CountlyCrashData : NSObject
 
 @property (nonatomic, copy, nonnull) NSString *stackTrace;
 @property (nonatomic, copy, nonnull) NSString *name;
@@ -487,7 +513,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th
     Swift
   
   
-
#import "Countly.h"
+    
#import "Countly.h"
 
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
@@ -538,7 +564,7 @@ Countly.sharedInstance().recordError("ERROR_NAME", isFatal: true, stackTrace: Th
 }
-
config.shouldUsePLCrashReporter = YES;
+
config.shouldUsePLCrashReporter = YES;

@@ -622,14 +649,28 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

Existence of PLCrashReporter dependency will be checked using - __has_include(<CrashReporter/CrashReporter.h>) preprocessor macro. + __has_include(<CrashReporter/CrashReporter.h>) preprocessor macro.

Note: Countly-PL.podspecautomatically manages the PLCrashReporter dependencies. However, if you encounter an error related to PLCrashReporter when using CocoaPods, you can resolve it by adding the following to your Podfile:

-
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if target.name == "Countly"
config.build_settings['OTHER_LDFLAGS'] ||= ['$(inherited)']
config.build_settings['OTHER_LDFLAGS'] << '-framework "CrashReporter"'

config.build_settings['LIBRARY_SEARCH_PATHS'] ||= ['$(inherited)']
config.build_settings['LIBRARY_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= ['$(inherited)']
config.build_settings['FRAMEWORK_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
end
end
end
end
+
post_install do |installer|
+ installer.pods_project.targets.each do |target|
+  target.build_configurations.each do |config|
+   if target.name == "Countly"
+       config.build_settings['OTHER_LDFLAGS'] ||= ['$(inherited)']
+       config.build_settings['OTHER_LDFLAGS'] << '-framework "CrashReporter"'
+
+       config.build_settings['LIBRARY_SEARCH_PATHS'] ||= ['$(inherited)']
+       config.build_settings['LIBRARY_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
+       config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= ['$(inherited)']
+       config.build_settings['FRAMEWORK_SEARCH_PATHS'] << "${PODS_XCFRAMEWORKS_BUILD_DIR}/PLCrashReporter"
+     end
+   end
+ end
+end

Note: PLCrashReporter option is available only for iOS apps.

@@ -644,15 +685,14 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

1) BSD: - PLCrashReporterSignalHandlerTypeBSD + PLCrashReporterSignalHandlerTypeBSD

2) Mach: - PLCrashReporterSignalHandlerTypeMach + PLCrashReporterSignalHandlerTypeMach

- For more information about PLCrashReporter please see: - https://github.com/microsoft/plcrashreporter + For more information about PLCrashReporter please see: https://github.com/microsoft/plcrashreporter

By default, BSD type will be used. For using Mach type signal handler with PLCrashReporter @@ -665,23 +705,24 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau Swift

-
config.shouldUseMachSignalHandler = YES;
+
config.shouldUseMachSignalHandler = YES;

PLCrashReporter Callback Blocks

There is a - crashOccuredOnPreviousSessionCallback + crashOccuredOnPreviousSessionCallback block to be executed when the app is launched again following a crash which is detected by PLCrashReporter on the previous session. It has an - NSDictionary parameter that - represents crash report object. If shouldUsePLCrashReporter + NSDictionary parameter that + represents crash report object. If + shouldUsePLCrashReporter flag is not set on initial config, this block will never be executed. You can - set it - on the CountlyConfig object: + set it on the CountlyConfig object:

@@ -689,28 +730,31 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau Swift
-
config.crashOccuredOnPreviousSessionCallback = ^(NSDictionary * crashReport)
+    
config.crashOccuredOnPreviousSessionCallback = ^(NSDictionary * crashReport)
 {  
   NSLog(@"crash report: %@", crashReport);
 };

There is also another - shouldSendCrashReportCallback + shouldSendCrashReportCallback block to be executed to decide whether the crash report detected by PLCrashReporter on the previous session should be sent to Countly Server or not. If not set, crash report will be sent to Countly Server by default. If set, crash report will be sent to Countly Server only if - YES is returned. It has an - NSDictionary parameter that - represents crash report object. If shouldUsePLCrashReporter + YES is returned. It has an + NSDictionary parameter that + represents crash report object. If + shouldUsePLCrashReporter flag is not set on initial config, this block will never be executed. You can - set it - on the CountlyConfig object: + set it on the CountlyConfig object:

@@ -718,18 +762,25 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau Swift
-
config.shouldSendCrashReportCallback = ^(NSDictionary * crashReport)
+    
config.shouldSendCrashReportCallback = ^(NSDictionary * crashReport)
 {                                                                                                                              NSLog(@"crash report: %@", crashReport);
   return YES;    //NO;
 };

Symbolication

- Enterprise Edition Feature +

+ Enterprise Edition Feature +

This feature is only available with an Enterprise Edition and built-in @@ -745,7 +796,7 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

Automatic dSYM Uploading

- For Automatic dSYM Uploading, you can use the countly_dsym_uploader script in the Countly iOS SDK. + For Automatic dSYM Uploading, you can use the countly_dsym_uploader script in the Countly iOS SDK.

To do so, go to the Build Phases @@ -756,15 +807,15 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau

Then, add the following snippet:

-
COUNTLY_DSYM_UPLOADER=$(/usr/bin/find $SRCROOT -name "countly_dsym_uploader.sh" | head -n 1)
+
COUNTLY_DSYM_UPLOADER=$(/usr/bin/find $SRCROOT -name "countly_dsym_uploader.sh" | head -n 1)
 sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"

Starting from Xcode 15 you would need to add an Input Files entry to the Run Script section like this:

-
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}
+
${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}

- This will make sure the symbolication folder is usable for the script. + This will make sure the symbolication folder is usable for the script.

Next, select the checkboxRun script only when installing. @@ -776,7 +827,7 @@ sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"< Note: Do not forget to replace your server and app key.

- By default, Xcode will generate dSYM files for the Release build configuration, and the countly_dsym_uploader script will handle the uploading automatically. You can check for the results on the Report Navigator within Xcode. If the dSYM upload has completed successfully, you will see the[Countly] dSYM upload successfully completed.message. + By default, Xcode will generate dSYM files for the Release build configuration, and the countly_dsym_uploader script will handle the uploading automatically. You can check for the results on the Report Navigator within Xcode. If the dSYM upload has completed successfully, you will see the[Countly] dSYM upload successfully completed.message.

@@ -793,10 +844,10 @@ sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"< If Bitcode is enabled in your project while uploading your app to App Store Connect, Apple re-compiles your app to optimize it for specific devices. When Apple re-compiles your app, a new dSYM file is generated for the new build, and the dSYM file on your machine will not work for symbolication. So, you will need to receive this new dSYM file manually, then upload it to the Countly Server. In order to get the new dSYM file, you can use App Store Connect or Xcode Organizer.

- Using App Store Connect: 1. Login to App Store Connect. 2. Go to the Activitytab. 3. Select your app's Version and Build 4. Under General Information click on Download dSYM. 5. If the downloaded file does not have any extension, add .zip and unarchive to see its content. + Using App Store Connect: 1. Login to App Store Connect. 2. Go to the Activitytab. 3. Select your app's Version and Build 4. Under General Information click on Download dSYM. 5. If the downloaded file does not have any extension, add .zip and unarchive to see its content.

- Using Xcode: 1. Open Organizer in Xcode. 2. Go to the Archives tab. 3. Select your app from the list on the left and select the archive. 4. Click on Download dSYMs.... 5. Xcode inserts the downloaded .dSYM files into the selected archive. + Using Xcode: 1. Open Organizer in Xcode. 2. Go to the Archives tab. 3. Select your app from the list on the left and select the archive. 4. Click on Download dSYMs.... 5. Xcode inserts the downloaded .dSYM files into the selected archive.

For more information regarding downloading dSYM files from Apple, please see Apple's documentation here. @@ -806,18 +857,18 @@ sh "$COUNTLY_DSYM_UPLOADER" "https://YOUR_COUNTLY_SERVER" "YOUR_APP_KEY"<

How to Use Symbolication

- Once your dSYM file has been uploaded to the Countly Server, you can symbolicate your crash reports coming from that build on the Crashespanel of your Countly Server. + Once your dSYM file has been uploaded to the Countly Server, you can symbolicate your crash reports coming from that build on the Crashespanel of your Countly Server.

A crash report symbolicated stack trace appears as follows:

Before symbolication:

-
YourAppName                               0x000000010006e174 YourAppName + 156020
+
YourAppName                               0x000000010006e174 YourAppName + 156020
 YourAppName                               0x000000010006d060 YourAppName + 151648
 YourAppName                               0x000000010006ad34 YourAppName + 142644
 

After symbolication:

-
-[MHViewController countlyProductionTest] (in YourAppName) (MHViewController.m:620)
+
-[MHViewController countlyProductionTest] (in YourAppName) (MHViewController.m:620)
 -[MHViewController transitionToMahya] (in YourAppName) (MHViewController.m:443)
 -[MHViewController textFieldShouldReturn:] (in YourAppName) (MHViewController.m:210)

@@ -832,7 +883,7 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 We have recorded an event named purchase with different scenarios in the examples below:

    -
  • +
  • purchase event occurred 1 time
@@ -842,14 +893,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
-
[Countly.sharedInstance recordEvent:@"purchase"];
+
[Countly.sharedInstance recordEvent:@"purchase"];
    -
  • +
  • purchase event occurred 3 times
@@ -859,14 +910,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
-
[Countly.sharedInstance recordEvent:@"purchase" count:3];
+
[Countly.sharedInstance recordEvent:@"purchase" count:3];
    -
  • +
  • purchase event occurred 1 times with the total amount of 3.33
  • @@ -877,14 +928,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
    -
    [Countly.sharedInstance recordEvent:@"purchase" sum:3.33];
    +
    [Countly.sharedInstance recordEvent:@"purchase" sum:3.33];
      -
    • +
    • purchase event occurred 3 times with the total amount of 9.99
    • @@ -895,14 +946,14 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
      -
      [Countly.sharedInstance recordEvent:@"purchase" count:3 sum:9.99];
      +
      [Countly.sharedInstance recordEvent:@"purchase" count:3 sum:9.99];
        -
      • +
      • purchase event occurred 1 time from country : Germany, on app_version : 1.0 @@ -914,18 +965,18 @@ YourAppName 0x000000010006ad34 YourAppName + 14264 Swift
        -
        NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
        +    
        NSDictionary* dict = @{@"country":@"Germany", @"app_version":@"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
         
         [Countly.sharedInstance recordEvent:@"purchase" segmentation:dict];
          -
        • +
        • purchase event occurred 2 times from country : Germany, on app_version : 1.0 @@ -937,18 +988,18 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict)
Swift
-
NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
+    
NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
 
 [Countly.sharedInstance recordEvent:@"purchase" segmentation:dict count:2];
    -
  • +
  • purchase event occurred 2 times with the total amount of 6.66, from country: Germany, on app_version : @@ -961,12 +1012,12 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2)Swift
    -
    NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
    +    
    NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
     
     [Countly.sharedInstance recordEvent:@"purchase" segmentation:dict count:2 sum:6.66];
    @@ -976,7 +1027,7 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum In the examples below, we recorded a timed event called level24 to track how long it takes to complete:

      -
    • +
    • level24 started
    @@ -986,14 +1037,14 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum Swift
    -
    [Countly.sharedInstance startEvent:@"level24"];
    +
    [Countly.sharedInstance startEvent:@"level24"];
      -
    • +
    • level24 ended
    @@ -1003,17 +1054,17 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum Swift
    -
    [Countly.sharedInstance endEvent:@"level24"];
    +
    [Countly.sharedInstance endEvent:@"level24"];

    Additionally, you can provide more information, such as the segmentation, count, and sum while ending an event.

      -
    • +
    • level24 ended 1 time with the total point of 34578, from country : Germany, on app_version : @@ -1026,18 +1077,18 @@ Countly.sharedInstance().recordEvent("purchase", segmentation:dict, count:2, sum Swift
      -
      NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
      +    
      NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
       
       [Countly.sharedInstance endEvent:@"level24" segmentation:dict count:1 sum:34578];

      - The duration of the event will be calculated automatically when the endEvent method is called. + The duration of the event will be calculated automatically when the endEvent method is called.

      You can also cancel a started timed event using cancelEvent method: @@ -1048,17 +1099,17 @@ Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:345

      -
      [Countly.sharedInstance cancelEvent:@"level24"];
      +
      [Countly.sharedInstance cancelEvent:@"level24"];

      Or, if you are measuring the duration of an event yourself, you can record it directly as follows:

        -
      • +
      • level24 took 344 seconds to complete:
      @@ -1068,17 +1119,17 @@ Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:345 Swift
      -
      [Countly.sharedInstance recordEvent:@"level24" duration:344];
      +
      [Countly.sharedInstance recordEvent:@"level24" duration:344];

      Additionally, you can provide more information such as the segmentation, count, and sum.

        -
      • +
      • level24 took 344 seconds to complete 2 times with the total point of 34578, from country : Germany, on @@ -1091,18 +1142,20 @@ Countly.sharedInstance().endEvent("level24", segmentation:dict, count:1, sum:345 Swift
        -
        NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
        +    
        NSDictionary* dict = @{@"country": @"Germany", @"app_version": @"1.0", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]};
         
         [Countly.sharedInstance recordEvent:@"level24" segmentation:dict count:2 sum:34578 duration:344];
        - Event Names and Segmentation +

        + Event Names and Segmentation +

        Event names must be non-zero length valid NSString and segmentation must be an NSDictionary which @@ -1113,11 +1166,11 @@ Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum:

        Sessions

        Automatic Session Tracking

        - By default, the Countly iOS SDK tracks sessions automatically and sends the begin_sessionrequest upon initialization, the end_session request when the app goes to the background, and the begin_session request again when the app comes back to the foreground. In addition, the Countly iOS SDK automatically sends a periodical (60 sec by default) update session request while the app is in the foreground. + By default, the Countly iOS SDK tracks sessions automatically and sends the begin_sessionrequest upon initialization, the end_session request when the app goes to the background, and the begin_session request again when the app comes back to the foreground. In addition, the Countly iOS SDK automatically sends a periodical (60 sec by default) update session request while the app is in the foreground.

        Manual Sessions

        - You can set the manualSessionHandling flag on the CountlyConfig object before starting Countly to handle sessions manually. + You can set the manualSessionHandling flag on the CountlyConfig object before starting Countly to handle sessions manually.

        @@ -1125,14 +1178,14 @@ Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum: Swift
        -
        config.manualSessionHandling = YES;
        +
        config.manualSessionHandling = YES;

        - If the manualSessionHandling flag is set, the Countly iOS SDK does not send the previously mentioned requests automatically, meaning you will need to manually call the beginSession, updateSession and endSession methods after you start Countly, depending on your own definition of a session. + If the manualSessionHandling flag is set, the Countly iOS SDK does not send the previously mentioned requests automatically, meaning you will need to manually call the beginSession, updateSession and endSession methods after you start Countly, depending on your own definition of a session.

        @@ -1140,19 +1193,19 @@ Countly.sharedInstance().recordEvent("level24", segmentation:dict, count:2, sum: Swift
        -
        [Countly.sharedInstance beginSession];
        +    
        [Countly.sharedInstance beginSession];
         [Countly.sharedInstance updateSession];
         [Countly.sharedInstance endSession];

        Update Session Period

        - You can specify the updateSessionPeriod on the CountlyConfig object before starting Countly. It is used for session updating and periodically sending queued events to the server. If the updateSessionPeriod is not explicitly set, the default setting will be at 60 seconds for iOS, tvOS & macOS, and 20 seconds for watchOS. + You can specify the updateSessionPeriod on the CountlyConfig object before starting Countly. It is used for session updating and periodically sending queued events to the server. If the updateSessionPeriod is not explicitly set, the default setting will be at 60 seconds for iOS, tvOS & macOS, and 20 seconds for watchOS.

        @@ -1160,16 +1213,16 @@ Countly.sharedInstance().endSession()
Swift
-
config.updateSessionPeriod = 300;
+
config.updateSessionPeriod = 300;

View Tracking

Automatic Views

- To enable automatic view tracking, you will need to set the enableAutomaticViewTracking flag on the CountlyConfig object before starting Countly. + To enable automatic view tracking, you will need to set the enableAutomaticViewTracking flag on the CountlyConfig object before starting Countly.

@@ -1177,21 +1230,21 @@ Countly.sharedInstance().endSession()
Swift
-
config.enableAutomaticViewTracking = YES;
+
config.enableAutomaticViewTracking = YES;

- After this step, the Countly iOS SDK will automatically track views by simply intercepting the viewDidAppear: method of the UIViewControllerclass and reporting which view is displayed with the view name and duration. If the view controller's title property is set, it would be reported as the view name. Otherwise, the view name will be the view controller's class name. + After this step, the Countly iOS SDK will automatically track views by simply intercepting the viewDidAppear: method of the UIViewControllerclass and reporting which view is displayed with the view name and duration. If the view controller's title property is set, it would be reported as the view name. Otherwise, the view name will be the view controller's class name.

Automatic View Exceptions

Default Exceptions for Automatic View Tracking

Following system view controllers will be excluded by default from automatic view tracking, as they are not visible to the user but rather structural controllers:

-
UINavigationController
+
UINavigationController
 UIAlertController
 UIPageViewController
 UITabBarController
@@ -1232,7 +1285,7 @@ UIKeyCommandDiscoverabilityHUDViewController
 

Custom Exceptions for Automatic View Tracking

- In addition to these default exceptions, you can manually set an exclusion list of the view controllers you don't want to track by using the automaticViewTrackingExclusionList array on the CountlyConfig object before starting Countly + In addition to these default exceptions, you can manually set an exclusion list of the view controllers you don't want to track by using the automaticViewTrackingExclusionList array on the CountlyConfig object before starting Countly

@@ -1240,10 +1293,10 @@ UIKeyCommandDiscoverabilityHUDViewController Swift
-
config.automaticViewTrackingExclusionList = @[NSStringFromClass(MyViewController.class), @"MyViewControllerName"];
+
config.automaticViewTrackingExclusionList = @[NSStringFromClass(MyViewController.class), @"MyViewControllerName"];

@@ -1260,13 +1313,13 @@ UIKeyCommandDiscoverabilityHUDViewController Swift

-
//Make your view controller to conform CountlyAutoViewTrackingName protocol.
+    
//Make your view controller to conform CountlyAutoViewTrackingName protocol.
 @interface MyViewController : UIViewController @end 
 //and implement countlyAutoViewTrackingName method to return custom view name to be used by Auto View Tracking.
 - (NSString *)countlyAutoViewTrackingName { return @"This is overridden custom view name"; }
-
[Countly.sharedInstance.views startAutoStoppedView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
+
[Countly.sharedInstance.views startAutoStoppedView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

Regular Views

Opposed to "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 manually start a view using the startView:method with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName: or stopViewWithID: + can manually start a view using the startView:method with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName: or stopViewWithID:

@@ -1326,10 +1379,10 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views startView:@"MyView"];
+
[Countly.sharedInstance.views startView:@"MyView"];

@@ -1341,10 +1394,10 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift

-
[Countly.sharedInstance.views startView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
+
[Countly.sharedInstance.views startView:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

Stopping Views

@@ -1354,7 +1407,7 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus

You can stop view tracking by its name using - stopViewWithName: + stopViewWithName:

@@ -1362,15 +1415,16 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views stopViewWithName:@"MyView"];
+
[Countly.sharedInstance.views stopViewWithName:@"MyView"];

This function allows you to manually stop the tracking of a view identified by - its name.
You can also specify the custom segmentation key-value pairs while stopping views:
+ its name.
+ You can also specify the custom segmentation key-value pairs while stopping views:

@@ -1378,15 +1432,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views stopViewWithName:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
+
[Countly.sharedInstance.views stopViewWithName:@"MyView" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

You can also stop view tracking by its unique idetifier using - stopViewWithID: + stopViewWithID:

@@ -1394,15 +1448,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views stopViewWithID:@"VIEW_ID"];
+
[Countly.sharedInstance.views stopViewWithID:@"VIEW_ID"];

This function allows you to manually stop the tracking of a view identified by - its unique identifier.
+ its unique identifier.
You can also specify the custom segmentation key-value pairs while stopping views:

@@ -1411,15 +1465,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views stopViewWithID:@"VIEW_ID" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
+
[Countly.sharedInstance.views stopViewWithID:@"VIEW_ID" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

You can stop all views tracking using - stopAllViews: + stopAllViews:

@@ -1427,14 +1481,15 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views stopAllViews:@{@"key": @"value"}];
+
[Countly.sharedInstance.views stopAllViews:@{@"key": @"value"}];

- This function stops the tracking of all views.
+ This function stops the tracking of all views.
+  

Pausing and Resuming Views

@@ -1446,7 +1501,7 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus

You can pause view tracking by its unique identifier using - pauseViewWithID: + pauseViewWithID:

@@ -1454,17 +1509,18 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views pauseViewWithID:@"VIEW_ID"];
+
[Countly.sharedInstance.views pauseViewWithID:@"VIEW_ID"];

- This function temporarily pauses the tracking of a view identified by its unique identifier. + This function temporarily pauses the tracking of a view identified by its unique + identifier.

- You can resume view tracking by its unique identifier using resumeViewWithID: + You can resume view tracking by its unique identifier using resumeViewWithID:

@@ -1472,21 +1528,20 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
[Countly.sharedInstance.views resumeViewWithID:@"VIEW_ID"];
+
[Countly.sharedInstance.views resumeViewWithID:@"VIEW_ID"];

This function resumes the tracking of a previously paused view identified by its unique identifier.

-

- Adding Segmentation to Started Views -

+

Adding Segmentation to Started Views

- You can also add segmentation to already started views using view name or view ID:
+ You can also add segmentation to already started views using view name or view ID:
+  

@@ -1494,13 +1549,13 @@ func countlyAutoViewTrackingName() -> String { return "This is overridden cus Swift
-
NSString * viewID = [Countly.sharedInstance.views startView:@"VIEW_NAME"];
+    
NSString * viewID = [Countly.sharedInstance.views startView:@"VIEW_NAME"];
 [Countly.sharedInstance.views addSegmentationToViewWithID:viewID segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
       
 [Countly.sharedInstance.views addSegmentationToViewWithName:@"VIEW_NAME" segmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

Global View Segmentation

- You can set global segmentation for views by using setGlobalViewSegmentation: + You can set global segmentation for views by using setGlobalViewSegmentation:

@@ -1516,14 +1571,14 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
[Countly.sharedInstance.views setGlobalViewSegmentation:@{@"key": @"value"}];
+
[Countly.sharedInstance.views setGlobalViewSegmentation:@{@"key": @"value"}];

- You can also update global segmentation values for views by using updateGlobalViewSegmentation: + You can also update global segmentation values for views by using updateGlobalViewSegmentation:

@@ -1531,18 +1586,18 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
[Countly.sharedInstance.views updateGlobalViewSegmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];
+
[Countly.sharedInstance.views updateGlobalViewSegmentation:@{@"key": @"value", @"arrayKey": @[@"one", @2, @3.14], @"int": @5, @"bool": @YES, @"double": @3.14, @"intArr": @[@4, @5, @6]}];

Device ID Management

- It is a persistently stored random NSUUID string. + It is a persistently stored random NSUUID string.

- If you would like to use a custom device ID, you can set the deviceID property on the CountlyConfig object. If the deviceID property is not set explicitly, a default + If you would like to use a custom device ID, you can set the deviceID property on the CountlyConfig object. If the deviceID property is not set explicitly, a default device ID will be used depending on the platform.

@@ -1551,15 +1606,15 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
config.deviceID = @"customDeviceID";  //Optional custom device ID
+
config.deviceID = @"customDeviceID";  //Optional custom device ID

Note: - Once set, the device ID will be persistently stored on the device after the first app launch, and the deviceID property will be ignored on the following app launches, until the app is deleted and re-installed or a resetStoredDeviceID flag is set. For further details, please check the Resetting Stored Device ID section below. + Once set, the device ID will be persistently stored on the device after the first app launch, and the deviceID property will be ignored on the following app launches, until the app is deleted and re-installed or a resetStoredDeviceID flag is set. For further details, please check the Resetting Stored Device ID section below.

Changing Device ID

@@ -1570,7 +1625,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

- You can change the device ID on runtime after you start Countly. You can either allow the device to be counted as a new device or merge existing data on the server.

To set a new device ID based on the current device ID type, use the setID: method. If the current device ID type is CLYDeviceIDTypeCustom, it will be counted as a new device; otherwise, it will merge existing data on the server. With setID:, the SDK will automatically handle whether to merge the device ID or not.
+ You can change the device ID on runtime after you start Countly. You can either allow the device to be counted as a new device or merge existing data on the server.
+
+ To set a new device ID based on the current device ID type, use the setID: method. If the current device ID type is CLYDeviceIDTypeCustom, it will be counted as a new device; otherwise, it will merge existing data on the server. With setID:, the SDK will automatically handle whether to merge the device ID or not.
+  

@@ -1578,17 +1636,20 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
//Automatically handle whether to merge the device ID or not.
+    
//Automatically handle whether to merge the device ID or not.
 [Countly.sharedInstance setID:@"new_device_id"];
- Consent Reset on Device ID Change

- If device ID is changed again from a developer provided ID and requiresConsent flag was enabled, all previously given consents will be removed. This means that all features will cease to function until new consent has been given again for the new device ID. + Consent Reset on Device ID Change +

+

+ If device ID is changed again from a developer provided ID and requiresConsent flag was enabled, all previously given consents will be removed. This means that all features will cease to function until new consent has been given again for the new device ID.

Temporary Device ID

@@ -1610,10 +1671,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
[config enableTemporaryDeviceIDMode];
+
[config enableTemporaryDeviceIDMode];

@@ -1625,10 +1686,11 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift

-
[Countly.sharedInstance enableTemporaryDeviceIDMode];
+
[Countly.sharedInstance enableTemporaryDeviceIDMode];
+

@@ -1641,7 +1703,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

Later, when the real device ID is set using - setID: method, all requests + setID: method, all requests which have been kept on hold until that point will start with the real device ID:

@@ -1651,16 +1713,18 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
[Countly.sharedInstance setID:@"new_device_id"];
+
[Countly.sharedInstance setID:@"new_device_id"];
- Consent Reset on Temporary Device ID Mode

- If the SDK goes into Temporary Device ID mode and requiresConsent flag was enabled, all previously given consents will be removed. Therefore after entering the Temporary Device ID mode, you should reestablish consent again. + Consent Reset on Temporary Device ID Mode +

+

+ If the SDK goes into Temporary Device ID mode and requiresConsent flag was enabled, all previously given consents will be removed. Therefore after entering the Temporary Device ID mode, you should reestablish consent again.

Retrieving Current Device ID

@@ -1673,10 +1737,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
[Countly.sharedInstance deviceID];
+
[Countly.sharedInstance deviceID];

@@ -1693,10 +1757,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift

-
[Countly.sharedInstance deviceIDType];
+
[Countly.sharedInstance deviceIDType];

@@ -1710,7 +1774,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

Resetting Stored Device ID

- In order to handle device ID changes for logged-in and logged-out users, the device ID specified in the CountlyConfig object of the deviceID property (or the default device ID, if not specified) will be persistently stored as well as the device ID passed to the changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method at any time upon the first app launch. By this point, until you delete and re-install the app, the Countly iOS SDK will continue to use the stored device ID and ignore the deviceID property. So, if you set the deviceID property to something different upon future app launches during development, it will have no effect. In this case, you can set the resetStoredDeviceID flag on the CountlyConfig object in order to reset the stored device ID. This will reset the initially stored device ID and the Countly iOS SDK will work as if it is the first app launch. + In order to handle device ID changes for logged-in and logged-out users, the device ID specified in the CountlyConfig object of the deviceID property (or the default device ID, if not specified) will be persistently stored as well as the device ID passed to the changeDeviceIDWithMerge: or changeDeviceIDWithoutMerge: method at any time upon the first app launch. By this point, until you delete and re-install the app, the Countly iOS SDK will continue to use the stored device ID and ignore the deviceID property. So, if you set the deviceID property to something different upon future app launches during development, it will have no effect. In this case, you can set the resetStoredDeviceID flag on the CountlyConfig object in order to reset the stored device ID. This will reset the initially stored device ID and the Countly iOS SDK will work as if it is the first app launch.

@@ -1718,14 +1782,14 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
config.resetStoredDeviceID = YES;
+
config.resetStoredDeviceID = YES;

- After you start Countly once with the resetStoredDeviceID flag while developing, you can remove that line. The resetStoredDeviceID flag is not meant for production. It is only for debugging purposes while performing development and not being able to delete and re-install the app. + After you start Countly once with the resetStoredDeviceID flag while developing, you can remove that line. The resetStoredDeviceID flag is not meant for production. It is only for debugging purposes while performing development and not being able to delete and re-install the app.

Push Notifications

@@ -1740,10 +1804,10 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView app and at your Countly server.

- First, you will need to acquire Push Notification credentials from Apple. (If you don't have them you can check this article to learn how you can do it.) + First, you will need to acquire Push Notification credentials from Apple. (If you don't have them you can check this article to learn how you can do it.)

- Then you would need to upload these credentials to your Countly server. You can refer to this article for learning how you can do that. + Then you would need to upload these credential to your Countly server. You can refer to this article for learning how you can do that.

Lastly you will need to integrate and enable the feature in your SDK as explained below. @@ -1760,7 +1824,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

Enabling Push

- Now, start Countly in the application:didFinishLaunchingWithOptions: method of your app with the following configuration. Do not forget to specify CLYPushNotifications in the features array on the CountlyConfig object. Then you'll need to ask for user's permission for push notifications using the Countly askForNotificationPermission method at any point in the app. The Countly iOS SDK will automatically handle the rest. No need to call any other method for registering when a device token is generated, or a push notification is received. + Now, start Countly in the application:didFinishLaunchingWithOptions: method of your app with the following configuration. Do not forget to specify CLYPushNotifications in the features array on the CountlyConfig object. Then you'll need to ask for user's permission for push notifications using the Countly askForNotificationPermission method at any point in the app. The Countly iOS SDK will automatically handle the rest. No need to call any other method for registering when a device token is generated, or a push notification is received.

@@ -1768,7 +1832,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
#import "Countly.h"
+    
#import "Countly.h"
 
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
@@ -1791,7 +1855,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
 }

- Note: Ensure you code-sign your application using the explicit Provisioning Profile specific to your app's bundleID with an aps-environment key in it. You can get it from the iOS Provisioning Profiles section of the Apple Developer website. Be advised, wildcard (*) profiles or profiles aps-environment key do not work with APNs, and the device can not receive a push token. + Note: Ensure you code-sign your application using the explicit Provisioning Profile specific to your app's bundleID with an aps-environment key in it. You can get it from the iOS Provisioning Profiles section of the Apple Developer website. Be advised, wildcard (*) profiles or profiles aps-environment key do not work with APNs, and the device can not receive a push token.

Note: Please make sure you do not set @@ -1841,22 +1905,22 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

Deep links

- When you send a push notification with custom actions buttons, you can redirect users to any custom page or view in your app by specifying deep links as custom actions button URLs. To do so, you will first need to create a URL scheme (e.g. : myapp://) in your project. + When you send a push notification with custom actions buttons, you can redirect users to any custom page or view in your app by specifying deep links as custom actions button URLs. To do so, you will first need to create a URL scheme (e.g. : myapp://) in your project.

- To do so, select your app target in Xcode and open the Info tab. Then, open the URL Types section by clicking the horizontal arrow, and click the plus + sign there. + To do so, select your app target in Xcode and open the Info tab. Then, open the URL Types section by clicking the horizontal arrow, and click the plus + sign there.

- Enter an identifier (preferably in reverse domain format) into the Identifier field and enter your app's URL scheme (without ://part) into the URL Schemes field. Optionally, you can set an Icon. You can leave the Role field as whatever its default value is. When you are done, you can confirm that your new URL scheme has been added to your app's Info.plist file. It should look like this: + Enter an identifier (preferably in reverse domain format) into the Identifier field and enter your app's URL scheme (without ://part) into the URL Schemes field. Optionally, you can set an Icon. You can leave the Role field as whatever its default value is. When you are done, you can confirm that your new URL scheme has been added to your app's Info.plist file. It should look like this:

- After setting up the URL scheme, you should add the application:openURL:options: method to your app delegate: + After setting up the URL scheme, you should add the application:openURL:options: method to your app delegate:

@@ -1864,7 +1928,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
+    
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
 {
   //handle URL here to navigate to custom views
 
@@ -1872,7 +1936,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
 }

- If your app's deployment target is lower than iOS9, you should add the application:openURL:sourceApplication:annotation: method instead: + If your app's deployment target is lower than iOS9, you should add the application:openURL:sourceApplication:annotation: method instead:

@@ -1889,7 +1953,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
+    
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation
 {
   //handle URL here to navigate to custom views
 
@@ -1897,7 +1961,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
 }

- Then in this method, you can check the passed url for custom view navigation using thescheme and host properties. For example, if you set the custom action button URLs as countly://productA and countly://productB, you can use something similar to this snippet: + Then in this method, you can check the passed url for custom view navigation using thescheme and host properties. For example, if you set the custom action button URLs as countly://productA and countly://productB, you can use something similar to this snippet:

@@ -1914,7 +1978,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
if ([url.scheme isEqualToString: @"countly"])
+    
if ([url.scheme isEqualToString: @"countly"])
 {
   if ([url.host isEqualToString: @"productA"])
   {
@@ -1929,7 +1993,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
 }

- Use the Product Name field of the Notification Service Extension target as you wish (for example: CountlyNSE) and ensure the Team is also selected. + Use the Product Name field of the Notification Service Extension target as you wish (for example: CountlyNSE) and ensure the Team is also selected.

Note: @@ -1983,7 +2047,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

Note: - If you cannot see the CountlyNotificationService.m file because you are using CocoaPods or Carthage for integration, please locate it yourself (probably under the Pods folder) and add it to your project manually. + If you cannot see the CountlyNotificationService.m file because you are using CocoaPods or Carthage for integration, please locate it yourself (probably under the Pods folder) and add it to your project manually.

@@ -1991,7 +2055,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView

Then find the NotificationService.m file (NotificationService.swift in Swift projects) - in the extension target. It is a default template file added automatically by Xcode. Import CountlyNotificationService.h inside this file. + in the extension target. It is a default template file added automatically by Xcode. Import CountlyNotificationService.h inside this file.

@@ -1999,16 +2063,15 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
#import "CountlyNotificationService.h"
+
#import "CountlyNotificationService.h"

- Then add the following line at the end of the - didReceiveNotificationRequest:withContentHandler: method as shown - below: + Then add the following line at the end of the didReceiveNotificationRequest:withContentHandler: + method as shown below:

@@ -2016,7 +2079,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift
-
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
+    
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
 {
   self.contentHandler = contentHandler;
   self.bestAttemptContent = [request.content mutableCopy];
@@ -2027,7 +2090,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
 

Note: - Please ensure you also configure the App Transport Security setting in the extension's Info.plist file just as with the main application. Otherwise, media attachments from non-https sources cannot be loaded. + Please ensure you also configure the App Transport Security setting in the extension's Info.plist file just as with the main application. Otherwise, media attachments from non-https sources cannot be loaded.

Note: - Please ensure you check that the Deployment Target version of the extension target is 10, not 10.3 (or whatever minor version Xcode set automatically). Otherwise, users running iOS versions lower than the Deployment Target value will not be able to get rich push notifications. + Please ensure you check that the Deployment Target version of the extension target is 10, not 10.3 (or whatever minor version Xcode set automatically). Otherwise, users running iOS versions lower than the Deployment Target value will not be able to get rich push notifications.

Provisional Permission for Push Notifications (iOS 12+ only) @@ -2065,7 +2128,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView Swift

-
UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionProvisional;
+    
UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionProvisional;
 
 [Countly.sharedInstance askForNotificationPermissionWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error)
 {
@@ -2074,7 +2137,7 @@ Countly.sharedInstance().views().addSegmentationToViewWithName(withName: "MyView
 }];

- If this is the only notification permission type for which you ask, there will be no permission dialog and it will be granted by default. Then, later on, the Notification Center users can swipe on these provisional notifications and cancel the provisional permission anytime they please. The notification permission level then changes to the UNAuthorizationStatusDenied from the UNAuthorizationStatusProvisional state. This functions is a kind of opt-out. + If this is the only notification permission type for which you ask, there will be no permission dialog and it will be granted by default. Then, later on, the Notification Center users can swipe on these provisional notifications and cancel the provisional permission anytime they please. The notification permission level then changes to the UNAuthorizationStatusDenied from the UNAuthorizationStatusProvisional state. This functions is a kind of opt-out.

How Push Notifications Work in Countly

When a push notification is received, the Countly iOS SDK handles everything automatically.

- First, it checks if the notification payload has the Countly specific dictionary (c key) and the notification ID inside it (ikey). If the Countly specific dictionary is present, it processes the notification. Otherwise, it does nothing. In both cases, the Countly iOS SDK forwards the notification to the default application delegate implementation for manual handling. + First, it checks if the notification payload has the Countly specific dictionary (c key) and the notification ID inside it (ikey). If the Countly specific dictionary is present, it processes the notification. Otherwise, it does nothing. In both cases, the Countly iOS SDK forwards the notification to the default application delegate implementation for manual handling.

The processing of the notification payload depends on the iOS version, the application’s status (background or foreground) at the time of notification reception, and the notification payload's content. @@ -2130,10 +2193,10 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti Swift

-
config.pushTestMode = CLYPushTestModeDevelopment;
+
config.pushTestMode = CLYPushTestModeDevelopment;

@@ -2149,10 +2212,10 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti Swift

-
config.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
+
config.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;

@@ -2166,7 +2229,7 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti

Disabling Alerts Shown by Notifications

- To disable messages from automatically being shown by the CLYPushNotifications feature while the app is in the foreground, you can set the doNotShowAlertForNotifications flag on the CountlyConfig object. If set, no message will be displayed by using the default system UI in the app, but push-open events will be recorded automatically. + To disable messages from automatically being shown by the CLYPushNotifications feature while the app is in the foreground, you can set the doNotShowAlertForNotifications flag on the CountlyConfig object. If set, no message will be displayed by using the default system UI in the app, but push-open events will be recorded automatically.

@@ -2174,23 +2237,23 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti Swift
-
config.doNotShowAlertForNotifications = YES;
+
config.doNotShowAlertForNotifications = YES;

Manually Handling Notifications

- If you would like to do additional custom work when a push notification is received, all you need to do is implement the default push-related methods in your application delegate (e.g. AppDelegate.m). After finishing its internal work, the Countly iOS SDK will push forward the related method calls to the default implementations on the application delegate. + If you would like to do additional custom work when a push notification is received, all you need to do is implement the default push-related methods in your application delegate (e.g. AppDelegate.m). After finishing its internal work, the Countly iOS SDK will push forward the related method calls to the default implementations on the application delegate.

- Please ensure you do not set theUNUserNotificationCenter.currentNotificationCenter's delegate manually, as the Countly iOS SDK will be acting as the delegate. All you need to do is directly add theUNUserNotificationCenterDelegate methods to your application delegate class. + Please ensure you do not set theUNUserNotificationCenter.currentNotificationCenter's delegate manually, as the Countly iOS SDK will be acting as the delegate. All you need to do is directly add theUNUserNotificationCenterDelegate methods to your application delegate class.

- Inside the push notification userInfodictionary you can find all the necessary information under the Countly Payload dictionary specified by the c (kCountlyPNKeyCountlyPayload) key. The array of the custom action buttons is specified by theb (kCountlyPNKeyButtons) key here, and each custom action button's title and action URL is specified by thet (kCountlyPNKeyActionButtonTitle) and l (kCountlyPNKeyActionButtonURL) keys, respectively. Here is an example of the Countly Push Notification Payload: + Inside the push notification userInfodictionary you can find all the necessary information under the Countly Payload dictionary specified by the c (kCountlyPNKeyCountlyPayload) key. The array of the custom action buttons is specified by theb (kCountlyPNKeyButtons) key here, and each custom action button's title and action URL is specified by thet (kCountlyPNKeyActionButtonTitle) and l (kCountlyPNKeyActionButtonURL) keys, respectively. Here is an example of the Countly Push Notification Payload:

-
{
+
{
   "aps":
   {
     "alert": "this is notification text",
@@ -2230,7 +2293,7 @@ Countly.sharedInstance().askForNotificationPermission(options: authorizationOpti
     Swift
   
   
-
NSDictionary* userInfo;     // notification dictionary
+    
NSDictionary* userInfo;     // notification dictionary
 NSInteger buttonIndex = 1;  // clicked button index
                             // 1 for first action button
                             // 2 for second action button
@@ -2239,7 +2302,7 @@ NSInteger buttonIndex = 1;  // clicked button index
 

Always Sending Push Tokens

- Thanks to iOS’ Remote Notification Background Mode, silent push notifications can be sent to users who have not given notification permission. However, the Countly iOS SDK does not send push tokens to the server by default from users who have not given permission for notifications. You can change this by setting the sendPushTokenAlways flag on the CountlyConfig object. If set, push tokens from all users, regardless of their notification permission status, will be sent to the Countly Server and these users will be listed as possible recipients on the Create Message screen of the Countly Dashboard. Be advised; these users can not be notified by an alert, sound, or badge. This is useful only for sending data via silent notifications. + Thanks to iOS’ Remote Notification Background Mode, silent push notifications can be sent to users who have not given notification permission. However, the Countly iOS SDK does not send push tokens to the server by default from users who have not given permission for notifications. You can change this by setting the sendPushTokenAlways flag on the CountlyConfig object. If set, push tokens from all users, regardless of their notification permission status, will be sent to the Countly Server and these users will be listed as possible recipients on the Create Message screen of the Countly Dashboard. Be advised; these users can not be notified by an alert, sound, or badge. This is useful only for sending data via silent notifications.

@@ -2258,15 +2321,15 @@ Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonInd Swift
-
config.sendPushTokenAlways = YES;
+
config.sendPushTokenAlways = YES;

Notification Permission with Preferred Types and Callback

- As asking for users’ permission for push notifications differ by iOS versions, the Countly iOS SDK has a one-liner convenience method, askForNotificationPermission, which does this for both iOS10 and older versions. It simply asks for a user's permission for all available notification types. However, if you need to specify which notification types your app will use (alert, badge, sound) or if you need a callback to see a user's response to the permission dialog, you can use theaskForNotificationPermissionWithOptions:completionHandler: + As asking for users’ permission for push notifications differ by iOS versions, the Countly iOS SDK has a one-liner convenience method, askForNotificationPermission, which does this for both iOS10 and older versions. It simply asks for a user's permission for all available notification types. However, if you need to specify which notification types your app will use (alert, badge, sound) or if you need a callback to see a user's response to the permission dialog, you can use theaskForNotificationPermissionWithOptions:completionHandler: method.

@@ -2275,7 +2338,7 @@ Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonInd Swift
-
UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert;
+    
UNAuthorizationOptions authorizationOptions = UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert;
 
 [Countly.sharedInstance askForNotificationPermissionWithOptions:authorizationOptions completionHandler:^(BOOL granted, NSError *error)
 {
@@ -2284,7 +2347,7 @@ Countly.sharedInstance().recordAction(forNotification:userInfo, clickedButtonInd
 }];
-
[Countly.sharedInstance disableLocationInfo];
+
[Countly.sharedInstance disableLocationInfo];

@@ -2530,9 +2588,7 @@ config.IP = "255.255.255.255"

Downloading Values

-

- Automatic Remote Config Triggers -

+

Automatic Remote Config Triggers

Automatic remote config triggers have been turned off by default; therefore, no remote config values will be requested without developer intervention.

@@ -2540,16 +2596,16 @@ config.IP = "255.255.255.255"
The automatic download triggers that would trigger a full value download are:

    -
  • +
  • when the SDK has finished initializing
  • -
  • +
  • after the device ID is changed without merging
  • -
  • +
  • when user gets out of temp ID mode
  • -
  • +
  • when 'remote-config' consent is given after it had been removed before (if consents are enabled)
@@ -2564,11 +2620,11 @@ config.IP = "255.255.255.255"
Swift
-
config.enableRemoteConfigAutomaticTriggers = YES;
+      
config.enableRemoteConfigAutomaticTriggers = YES;
 
@@ -2584,11 +2640,11 @@ config.IP = "255.255.255.255"
Swift
-
config.enableRemoteConfigValueCaching = YES;
+    
config.enableRemoteConfigValueCaching = YES;
 

Manual Calls

@@ -2596,19 +2652,19 @@ config.IP = "255.255.255.255"
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, omitting (everything except) keys.

Each of these calls also has an optional parameter that you can provide a RCDownloadCallback to, which would be triggered when the download attempt has finished.

- downloadKeys is the same as the automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones). + downloadKeys is the same as the automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones).

@@ -2616,17 +2672,19 @@ config.IP = "255.255.255.255" Swift
-
[Countly.sharedInstance.remoteConfig downloadKeys:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
//...
}];
+
[Countly.sharedInstance.remoteConfig downloadKeys:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
+   //...
+}];

- Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array. + Or you might only want to update specific key values. To do so, you will need to call downloadSpecificKeys to downloads new values for the wanted keys. Those are provided with a String array.

@@ -2634,15 +2692,19 @@ config.IP = "255.255.255.255" Swift
-
[Countly.sharedInstance.remoteConfig downloadSpecificKeys:NSArray *keys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
//...
}];
+
[Countly.sharedInstance.remoteConfig downloadSpecificKeys:NSArray *keys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
+   //...
+}];

- Or you might want to update all the values except a few defined keys. To do so,  call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array. + Or you might want to update all the values except a few defined keys. To do so, call downloadOmittingKeys would update all values except the provided keys. The keys are provided with a String array.

@@ -2650,10 +2712,14 @@ config.IP = "255.255.255.255" Swift
-
[Countly.sharedInstance.remoteConfig downloadOmittingKeys:NSArray *omitKeys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
//...
}];
+
[Countly.sharedInstance.remoteConfig downloadOmittingKeys:NSArray *omitKeys completionHandler:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
+   //...
+}];
@@ -2666,8 +2732,7 @@ config.IP = "255.255.255.255" key. This returns an CountlyRCData object that contains the value of the key and the metadata about that value's owner. If value in CountlyRCData was null - then no value was found or the value was null. -   + then no value was found or the value was null.

@@ -2675,10 +2740,18 @@ config.IP = "255.255.255.255" Swift
-
id value_1 = [Countly.sharedInstance.remoteConfig getValue:@"key_1"].value;
id value_2 = [Countly.sharedInstance.remoteConfig getValue:@"key_2"].value;
id value_3 = [Countly.sharedInstance.remoteConfig getValue:@"key_3"].value;
id value_4 = [Countly.sharedInstance.remoteConfig getValue:@"key_4"].value;

int intValue = [value_1 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_1 intValue] : 0;
double doubleValue = [value_2 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_2 doubleValue] : 0.0;
NSArray *jArray = [value_3 isKindOfClass:[NSArray class]] ? (NSArray*)value_3 : @[];
NSDictionary *jObj = [value_4 isKindOfClass:[NSDictionary class]] ? (NSDictionary*)value_4 : @{};
+
id value_1 = [Countly.sharedInstance.remoteConfig getValue:@"key_1"].value;
+id value_2 = [Countly.sharedInstance.remoteConfig getValue:@"key_2"].value;
+id value_3 = [Countly.sharedInstance.remoteConfig getValue:@"key_3"].value;
+id value_4 = [Countly.sharedInstance.remoteConfig getValue:@"key_4"].value;
+
+int intValue = [value_1 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_1 intValue] : 0;
+double doubleValue = [value_2 isKindOfClass:[NSNumber class]] ? [(NSNumber *)value_2 doubleValue] : 0.0;
+NSArray *jArray = [value_3 isKindOfClass:[NSArray class]] ? (NSArray*)value_3 : @[];
+NSDictionary *jObj = [value_4 isKindOfClass:[NSDictionary class]] ? (NSDictionary*)value_4 : @{};
@@ -2772,11 +2851,13 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:] Swift
-
[config remoteConfigRegisterGlobalCallback:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
// ...
}] +
[config remoteConfigRegisterGlobalCallback:^(CLYRequestResult _Nonnull response, NSError * _Nonnull error, BOOL fullValueUpdate, NSDictionary<NSString *,CountlyRCData *> * _Nonnull downloadedValues) {
+    // ...
+}]
 
@@ -2786,18 +2867,19 @@ let jObj = allValues["key_4"] as? [String: Any] ?? [:] and it would have the following parameters: