Windows

Follow

This document explains how to install Countly SDK for Windows desktop applications. It applies to version 24.1.X.

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

The Countly Windows SDK implements the following explicit flavors:

  • .NET Standard 2.0
  • .NET Framework 3.5, 4.5

The Countly GitHub page for this SDK contains also sample projects. You should be able to download them to test the basic functionality of this SDK and make sure you are using it correctly. In case you encounter any problems in your application,

The project page can be found here

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

  1. In Solution Explorer, right-click References and choose Manage NuGet Packages.image-NuGet-packages.png
  2. 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
  3. Accept any license prompts.

SDK Integration

Before you can use any Countly functionality, you need to call 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
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "https://xxx.count.ly";
cc.appKey = "YOUR_APP_KEY";
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.

Note: The SDK targets multiple profiles. Therefore for some of them, there are feature differences. Either with additional function calls or with additional fields in the CountlyConfig object.

If you are in doubt about the correctness of your Countly SDK integration you can learn about the verification methods from here.

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:

Step 1: Enable SDK logging using the following call:

    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.

  • Configuration: Debug
  • "Define DEBUG constant" is checked

mceclip1.png

Log messages written in the application will show up in 'Output' windows.

mceclip4.png

SDK Data Storage

Cached requests and other SDK relevant information is stored in files in a named folder. All platform targets except .net40 call that folder "countly", .net40 calls that folder "countly_data".

All platform targets except .net35 will use IsolatedStorage for that. .net35 will store that named folder in the same folder as the executable by default.

If there are permission limitations for your app targeting .net35, then there 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\");

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
{
  serverUrl = "SERVER_URL",
  appKey = "APP_KEY",
  appKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  appVersion = "1.2.3",
  application = referenceToApplication //provide link to your application
};

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.

Automatic Crash Handling

Automatic crash handling is possible if the project type / platform target supports a unhandled exception handler.

In that case you would subscribe to that and report the collected errors/exception to the Countly SDK.

Exception details and device properties would be sent on the next app launch.

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 use the following code snippet:

Dictionary<string, string> customInfo = new Dictionary<string, string>{
  { "customData", "importantStuff" }
};

try {
  throw new Exception("It is an exception");
} catch (Exception ex) {
  Countly.RecordException(ex.Message, ex.StackTrace, customInfo, false);
}

Here is the detail of the parameters:

  • error - A string that contains a detailed description of the exception.
  • stackTrace - A string that describes the contents of the call stack.
  • customInfo - Custom key/values to be reported.
  • unhandled - (bool) Set false if the error is fatal.

If you have handled an exception and it turns out to be fatal to your app, you may use the following calls:

Countly.RecordUnhandledException(ex.Message, ex.StackTrace, customInfo, true);

Crash Breadcrumbs

Throughout your app, you can leave crash breadcrumbs that would 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");

Consent

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.

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:

  • 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.
  • Duration - Used to record and track the duration of events. 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".

Recording Events

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:

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

1. Event key and count

await Countly.RecordEvent("purchase", 3);

2. Event key, count, and sum

await Countly.RecordEvent("purchase", 3, 0.99);

3. Event key and count with segmentation(s)

Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");

await Countly.RecordEvent("purchase", 3, segmentation);

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

Segmentation segmentation = new Segmentation();
segmentation.Add("country", "Germany");
segmentation.Add("app_version", "1.0");

await Countly.RecordEvent("purchase", 3, 2.97, segmentation);

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

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.

Timed Events

Timed events are events which gives you the ability to calculate the duration it takes for an event to take place with respect to any arbitrary point you choose. Timed events must be handled manually, where they need a call to start the event and another call to end the event. First call is used to start an internal timer which would continue counting until the second call is used to stop it and record the duration. This second call would end the timer, create an event with the given name and the with the duration from the timer and send it to the event queue. If this second call has not been called or if the app has been closed before calling it, no event would be created. As the timer is stored at the memory of the device, if you close the app before ending the event, you will have to start all over when you open the app later again.

string eventName = "Some event";

//start some event with the event name "Some event"
Countly.Instance.StartEvent(eventName);
//wait some time

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

string eventName = "Some event";

//start some event
Countly.Instance.StartEvent(eventName);
//wait some time

Segmentation segmentation = new Segmentation();
segmentation.Add("wall", "orange");

//end the event while also providing segmentation information
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 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
Countly.Instance.StartEvent(eventName);
//wait some time

//cancel the event
Countly.Instance.CancelEvent(eventName);

Consent

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.

Sessions

Manual Sessions

After you have initiated the SDK, call Countly.Instance.SessionBegin() when you want to start tracking the user session. Usually, that is called in the entry point of the app.

Call Countly.Instance.SessionEnd() before the app is closed to mark the end of the apps session.

The SDK will send an update request every 60 seconds, to track how long the session is.

That time is tracked using an internal timer. Some usage scenarios (like using the SDK in a console application) can prevent the timer from working. In those circumstances, you can manually call Countly.Instance.SessionUpdate(elapsedTime) to track the passage of time. You should still call it about every minute.

//start the user session
Countly.Instance.SessionBegin();

//end the user session
Countly.Instance.SessionEnd();

//update the session manually
int elapsedTime = 60;//elapsed time in seconds
Countly.Instance.SessionUpdate(elapsedTime);

View Tracking

Manual View Recording

The SDK provides a call to record views in your application. More information about how to use them can be found here. You only need to provide the name for the 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 generated by the SDK. It is then saved locally and then reused on every init unless the developer supplies its own device Id.

Ideally, the same device Id would be generated on the same device when using the same generation method. This way it would be possible to track users on reinstalls.

Device ID Generation

The SDK supports multiple ways for generating that ID, each with its pro's and 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.

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
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "http://YOUR_SERVER";
cc.appKey = "YOUR_APP_KEY";
cc.appVersion = "1.2.3";
cc.developerProvidedDeviceId = "use@email.com";

//initiate the SDK with your preferences
Countly.Instance.Init(cc);

Changing Device ID

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

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

Doing it this way, will not merge the previously acquired data with the new id.

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

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.

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

User Location

While integrating this SDK into your application, you might want to track your user location. You could use this information to better know your app’s 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).
  • Latitude and longitude values separated by a comma, e.g. "56.42345,123.45325".
  • Your user’s IP address.

Setting Location

Note that the IP address will only be updated if it was set during the init process.

During init, you can set location info in the configuration:

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
String gpsLocation = "63.445821, 10.898868";
String ipAddress = "13.56.33.12";
String country_code = "us";
String city = null;

Countly.Instance.SetLocation(gpsLocation, ipAddress, country_code, city);

If there are fields you don't want to set, set them to null. If you're going to reset a field, set it to an empty string.

If no location is provided, it will be approximated by using GeoIP.

Disabling Location

Users might want to opt-out of location tracking. To do so call:

//disable location tracking
Countly.Instance.DisableLocation();

This will also erase all location info server side.

User Profiles

This feature is available with an Enterprise Edition subscription. For information about User Profiles, review this documentation.

Setting Predefined Values

The SDK allows you to upload specific data related to a user to the Countly server. 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.

You can save info about user tracking data is related to. Countly provides Countly.UserDetails an object that exposes user-related properties

Each time you change a property value, Countly syncs it with a server. If you set value as null, you will delete the property.

Example:

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

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

Note: dots (.) and dollar signs ($) in key names will be stripped out.

User Consent

If you want to comply with GDPR or similar privacy requirements, there is functionality to manage user consent to features.

More information about GDPR can be found here.

Setup During Init

By default the requirement for consent is disabled. To enable it, you have to do it with the CountlyConfig object by setting consentRequired to true.

//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "http://YOUR_SERVER";
cc.appKey = "YOUR_APP_KEY";

//enable consent
cc.consentRequired = true;

//initiate the SDK with your preferences
Countly.Instance.Init(cc);

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

Consent for features is not persistent and will have to be set every time while initializing countly. Therefore, the storage and persistence of given consent fall on the SDK integrator.

Feature names in this SDK are stored as an enum called ConsentFeatures.

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

//create the Countly init object
CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "http://YOUR_SERVER";
cc.appKey = "YOUR_APP_KEY";

//enable consent
cc.consentRequired = true;

//set consent features
Dictionary<ConsentFeatures, bool> consent = new Dictionary<ConsentFeatures, bool>();
consent.Add(ConsentFeatures.Crashes, true);
consent.Add(ConsentFeatures.Events, false);
consent.Add(ConsentFeatures.Location, true);
consent.Add(ConsentFeatures.Sessions, false);
consent.Add(ConsentFeatures.Users, false);
cc.givenConsent = consent;

//initiate the SDK with your preferences
Countly.Instance.Init(cc);

Changing Consent

Consent can also be changed at any other moment in the app after init:

//preparing consent features
Dictionary<ConsentFeatures, bool> consent = new Dictionary<ConsentFeatures, bool>();
consent.Add(ConsentFeatures.Crashes, true);
consent.Add(ConsentFeatures.Events, false);
consent.Add(ConsentFeatures.Location, true);

//changing consent
Countly.Instance.SetConsent(consent);

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:

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.

sessionUpdateInterval - (Optional, int) Sets the interval (in seconds) after which the application will automatically extend the session. The default value is 60 (seconds).

SDK Internal Limits

SDK does have configurable fields to manipulate the internal SDK value and key limits. If values or keys provided by the user, would exceed the limits, they would be truncated. Here are the details of these configurable fields:

MaxKeyLength - (int) Maximum size of all string keys. The default value is 128.

MaxValueLength - (int) Maximum size of all values in our key-value pairs. The default value is 256.

MaxSegmentationValues - (int) Max amount of custom (dev provided) segmentation in one event. The default value is 256.

MaxStackTraceLinesPerThread - (int) Limits how many stack trace lines would be recorded per thread. The default value is 30.

MaxStackTraceLineLength - (int) Limits how many characters are allowed per stack trace line. The default value is 200.

MaxBreadcrumbCount - (int)maximum amount of breadcrumbs. The default value is 100.

Custom Metrics

This functionality is available since SDK version 24.1.0.

During some specific circumstances, like beginning a session or requesting remote config, the SDK is sending device metrics.

It is possible for you to either override the sent metrics (like the application version for some specific variant) or provide either your own custom metrics. If you are providing your own custom metrics, you would need your own custom plugin server-side which would interpret it appropriately. If there is no plugin to handle those custom values, they will be ignored.

IDictionary<string, string> metricOverride = new Dictionary<string, string>();
metricOverride["SomeKey"] = "123";
metricOverride["_locale"] = "xx_yy";

//create the Countly init object 
CountlyConfig cc = new CountlyConfig(); 
cc.serverUrl = "http://YOUR_SERVER"; 
cc.appKey = "YOUR_APP_KEY";
cc.SetMetricOverride(metricOverride);

//initiate the SDK with your preferences 
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.

This feature allows also a fine grain control over to which Countly app the data should be sent.

Backend mode is mainly intended for server/backend use cases.

When backend mode is enabled other SDK calls will be ignored.

When in backend mode, nothing is saved persistently and everything is stored only in memory.

The backend mode does not have checksum ability.

Enabling Backend Mode

To enable backend mode you need to call "EnableBackendMode" :

CountlyConfig cc = new CountlyConfig();
cc.serverUrl = "YOUR_SERVER_URL";
cc.appKey = "ONE_OF_YOUR_APP_KEYS";
cc.appVersion = "APP_VERSION";
cc.EnableBackendMode();
await Countly.Instance.Init(cc);

You would also provide the default appKey.

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

To report a crash with backend mode this method should be called:

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.

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

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:

- stackTrace: if not provided it will be not sent to the server

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

- metrics: if not provided it will be not sent to the server

- 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

- timestamp: if not provided, it will be set as current timestamp, ex. timestamp: 1703752478530

Here is a set of examples:

// unhandled crash reporting with metrics
var metrics = new Dictionary<string, string>(){
  {"_os", "Windows"},
  {"_os_version", "Windows10NT"},
  {"_run", "5678"},
  {"_bat", "67"},
  {"_ram_total", "1024"},
};

Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception", unhandled: true, metrics: metrics, appKey: APP_KEY);

// crash reporting with all params provided and custom segmentation
var customSegmentation = new Dictionary<string, string>(){
  {"level", 65},
  {"build_version", "45fA2022"},
  {"critical_point", -5.4E-79},
  {"timestamp", 1703752478530},
  {"done", true},
  {"percent", 0.67}
};
var stackTrace = ... // gather stack trace
var breadCrumbs = new List<string> { "Before Init", "After Init" };
  
Countly.Instance.BackendMode().RecordException(DEVICE_ID, "Exception", stackTrace, breadCrumbs, customSegmentation, metrics, appKey: APP_KEY);
  
// 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

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

Here are some examples for recording event with the backend mode:

BackendMode bm = Countly.Instance.BackendMode(); // for convenient calling
bm.RecordEvent("device1", "event1", appKey: "app1");
Segmentation segmentation = new Segmentation();
segmentation.Add("uid", "2873673");
long timestamp = 1702467348;
bm.RecordEvent("device2", "event2", segmentation, 2, 8.02, 30, "app2", timestamp);
bm.RecordEvent("device3", "event3", segmentation, appKey: "app3"); // timestamp will be set as current timestamp 

Sessions

Also sessions can be tracked with the Backend Mode. There are "BeginSession", "UpdateSession" and "EndSession" methods.

Begin Session

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.

Location can be also tracked with providing location parameter of the function. A device's location information can only be tracked with BeginSession call in backend mode.

If language and country information would like to be tracked, _locale metric 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
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)
var metrics = new Dictionary<string, string>(){
  {"_os", "Windows"},
  {"_os_version", "Windows10NT"},
  {"_locale", "en-US"},
  {"_resolution", "256x256"},
  {"_device", "ETab 4"},
  {"_carrier", "X-mobile"},
  {"_app_version", "1.0"},
};

var location = new Dictionary<string, string>(){
  {"location", "-0.3720234014105792,-159.99741809049596"},
  {"ip", "1.1.1.1"},
  {"city", "New York"},
  {"country_code", "DEU"} // iso country code
};
Countly.Instance.BackendMode().BeginSession(DEVICE_ID, APP_KEY, metrics, location, 1703752478530);

Update Session

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

Here are examples about UpdateSession method.

// minimal call to the UpdateSession, this fallbacks to internal metrics and app key
Countly.Instance.BackendMode().UpdateSession(DEVICE_ID, 60);

// with custom timestamp
Countly.Instance.BackendMode().UpdateSession(DEVICE_ID, 45, APP_KEY, 1703752478530);

End Session

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

Here are examples about EndSession method.

// 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
Countly.Instance.BackendMode().EndSession(DEVICE_ID, 45, APP_KEY, 1703752478530);

View Tracking

The Windows SDK backend mode provides manual reporting of views. There is no automatic handling of views internally.

The SDK provides two functions; StartView and StopView

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

name and segment parameters are required. They should not be empty or null.

Segment is platform for devices or domain for websites.

If a view is first view in the session or in the design of your flow, firstView parameter must be provided as true. Default is false.

Here are examples about StartView method.

// minimal call to the StartView
Countly.Instance.BackendMode().StartView(DEVICE_ID, "Login", segment: "Desktop");

Segmentation segmentation = new Segmentation();
segmentation.Add("email", "test@test.test");
segmentation.Add("campaign_user", "true");

// with segmentation, firstView and timestamp
Countly.Instance.BackendMode().StartView(DEVICE_ID, "Login", segmentation, "Desktop", APP_KEY, true, 1703752478530);

Stop View

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.

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
Countly.Instance.BackendMode().StopView(DEVICE_ID, "Logout", 34, segment: "Android");

Segmentation segmentation = new Segmentation();
segmentation.Add("last_seen", "1994-11-05T13:15:30Z");
segmentation.Add("campaign_user", "false");

// with segmentation and timestamp
Countly.Instance.BackendMode().StopView(DEVICE_ID, "Logout", 56, segmentation, "Gear", APP_KEY, 1703752478530);

Device ID Management

It is possible manage device ids with the Windows SDK backend mode.

Change Device ID With Merge

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
Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(NEW_ID, OLD_ID);

// with custom timestamp
Countly.Instance.BackendMode().ChangeDeviceIdWithMerge(NEW_ID, OLD_ID, APP_KEY, 1703752478530);

User Profiles

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

userProperties are required and should not be empty. Current supported data types for the values are: string, int, long, double, float and bool

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"

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
var userProperties = new Dictionary<string, object>(){
   {"name", "John"},
   {"username", "Dohn"},
   {"organization", "Fohn"},
   {"email", "johnjohn@john.jo"},
   {"phone", "+123456789"},
   {"gender", "Unkown"},
   {"byear", 1969},
   {"picture", "http://someurl.png"}
};
Countly.Instance.BackendMode().RecordUserProperties(DEVICE_ID, userProperties);

// with custom timestamp and custom properties
userProperties["int"] = 5;
userProperties["long"] = 1044151383000;
userProperties["float"] = 56.45678;
userProperties["string"] = "value";
userProperties["double"] = -5.4E-79;
userProperties["invalid"] = new List<string>(); // this will be eliminated
userProperties["action"] = "{$push: \"black\"}";
userProperties["nullable"] = null; // this will be eliminated
userProperties["marks"] = "{$inc: 1}";
userProperties["point"] = "{$mul: 1.89}"
userProperties["gpa"] = "{$min: 1.89}"
userProperties["gpa"] = "{$max: 1.89}"
userProperties["name"] = "{$setOnce: \"Name\"}"
userProperties["permissions"] = "{$pull: [\"Create\", \"Update\"]}"
userProperties["langs"] = "{$push: [\"Python\", \"Ruby\", \"Ruby\"]}" // this will create two 'Ruby' entry
userProperties["langs"] = "{$addToSet: [\"Python\", \"Python\"]}" // this will create only one 'Python' entry

Countly.Instance.BackendMode().RecordUserProperties(DEVICE_ID, userProperties, APP_KEY, 1703752478530);

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.

The keys for predefined modification operations are as follows:

Key Description Example Usage
$inc increment value by provided value props["age"] = "{$inc: 5}"
$mul multiply value by the provided value props["point"] = "{$mul: 1.89}"
$min sets minimum value between given and existing props["gpa"] = "{$min: 1.89}"
$max sets maximum value between given and existing props["gpa"] = "{$max: 1.89}"
$setOnce set value if it does not exist props["name"] = "{$setOnce: \"Name\"}"
$pull remove values from an array prop props["permissions"] = "{$pull: [\"Create\", \"Update\"]}"
$push insert values to an array prop, same values can be added props["langs"] = "{$push: [\"Python\", \"Ruby\"]}"
$addToSet insert values to an array of unique values, same values are ignored props["langs"] = "{$addToSet: [\"Python\", \"Python\"]}"

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

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
var parameters = new Dictionary<string, string>(){
   {"begin_session", "1"},
   {"metrics", ... }, // metrics to provide
   {"location", "-0.3720234014105792,-159.99741809049596" },
   {"sdk_custom_version", "24.1.0:04"},
   {"user_id", "123456789"},
   {"onesignal_id", "..."}
};
Countly.Instance.BackendMode().RecordDirectRequest(DEVICE_ID, parameters);

// with custom timestamp
Countly.Instance.BackendMode().RecordDirectRequest(DEVICE_ID, parameters, APP_KEY, 1703752478530);

Configuring Backend Mode

When backend mode is enabled, the SDK will apply limits to request queue and to the event queue. Default limit for the maximum amount of entries that the request queue can hold is 1000. When exceeding that count, the oldest request will be deleted.

Events are sent periodically (every 60 seconds we would try to send all stored events), and a subsection of events can be sent based on different triggers.

For a single device ID the default "sending" threshold is 10 events.

For all events targeted for a single app, the default "sending" threshold is 1000 events.

For all stored events for a target countly server the default "sending" threshold is 10000.

These limits can be changed with these additional config calls:

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

FAQ

What Information Is Collected by the SDK

The following description mentions 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
- Current hour
- Current day of week
- Current timezone
- SDK version
- SDK name

* If sessions are used then it would record the session start time, end time, and duration

* If sessions are used then also device metrics are collected which contains:
- Screen resolution
- Screen density
- OS name
- OS version
- App version
- Locale identifier

* When events are recorded, the following information collected:
- Time of event
- Current hour
- Current day of week

* If crash tracking is enabled, it will collect the following information at the time of the crash:
- OS name
- OS version

- Device resolution
- App version
- Time of the crash
- Crash stack trace
- Error description
- Total RAM

- If there is a network connection

Any other information like data in events, location, user profile information, or other manual requests depends on what the developer decides to provide and is not collected by the SDK itself.

Is Windows SDK Compatible With .Net Maui

.NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating native mobile and desktop apps with C# and XAML. It is compatible with .NET 6 runtime (or rather .NET Core 5). All .NET Core versions (2.0 and above) are compatible with .NET Standard 2.0 libraries.

As one of our Windows SDK flavors targets '.NET Standard' you should be able to use our Windows SDK in your .Net Maui applications without any hurdle.

However there a couple of issues to touch upon. On .NET MAUI applications, when an uncaught exception happens the AppDomain.UnhandledException event occurs. As it is a cross-platform framework this effects the each platform you target differently and each one should need. For Android applications all exceptions should go through the Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser event instead of the AppDomain.CurrentDomain.UnhandledException event. And for iOS and Mac Catalyst, handling of the UnwindNativeCode value is necessary.

So for reporting native crashes using this class in your project would provide a handler that would work on major platforms with the mentioned logic. To handle uncaught exceptions correctly we catch iOS and Android errors at lines 29 and 40 respectively. After catching them, we route all unhandled exceptions from different platforms through this event handler.

Usage:

MauiExceptions.UnhandledException += (sender, args) =>
{
 Countly.RecordException(args.ExceptionObject.ToString(), null, null, true).Wait();
};

Windows SDK GitHub page contains a sample project to test the basic functionality.

Looking for help?