This document explains how to install Countly SDK for Windows desktop applications. It applies to version 21.11.0.
Older documentation
To access the documentation for version 20.11 click here.
The Countly Windows SDK implements the following flavors:
- Universal Windows Platform
- .NET Standard, Version=2.0
- .NET Portable, Version=4.5
- .NET Framework, Version=v3.5
- .NET Framework, Version=v4.0, Profile=Client
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 in either your project file or a packages.config
file (depending on the project format).
- In Solution Explorer, right-click References and choose Manage NuGet Packages.
- Choose "nuget.org" as the Package source, select the Browse tab, search for Countly, select that package in the list, and select Install:
-
Accept any license prompts.
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 = "http://YOUR_SERVER";
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. For more information on how to acquire you application key (appKey) and server URL, check here.
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.
SDK logging / debug mode
The first thing you should do while integrating our SDK is 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
Log messages written in the application will show up in 'Output' windows.
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
Countly SDK has the ability to automatically collect crash reports which you can examine and resolve later on the server. You should subscribe to the unhandled exceptions handler manually. Exception details and device properties will 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 Mandatory 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.AddBreadCrumb("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
Currently, SDK doesn't have any direct mechanism to record timed Events. To record a timed event, you would have to calculate the duration of an event yourself. You could record the timestamp at the start of it and at the end, and then you would pass the calculated duration to Countly when you are recording the event.
Example:
//At the start of your planned event you would record the start timestamp
DateTime startTime = DateTime.UtcNow;
...
//Some time would pass and you would determine that your planned event has ended and you would record how many seconds passed
double duration = (DateTime.UtcNow - startTime).TotalSeconds;
//Then you would pass this information when recording a Countly event
await Countly.RecordEvent("purchase", 3, null, duration, null);
You may provide segmentation, count, and sum while recording a timed event.
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:
- cpuId - [net35, net40] (we recommend against using this) uses the OS-provided CPU id info to generate a hash that is used as an id. It should be possible to generate the same id on a reinstall if the CPU stays the same. On virtual machines and Windows 10 devices are not guaranteed to be unique and generate the same id and therefore device id conflicts
- multipleWindowsFields - [net35, net40] uses multiple OS-provided fields (CPU id, disk serial number, windows serial number, windows username, mac address) to generate a hash that would be used as the device Id. This method should regenerate the same id on a reinstall, provided those source fields do not change
- 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
After SDK initialization, you can set location info.
//set user location
String gpsLocation = "63.445821, 10.898868";
String ipAddress = "13.56.33.12";
String country_code = null;
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 want 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:
Countly.UserDetails.Name = "John";
// set name to JohnCountly.UserDetails.Name = "null";
// remove name
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.
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.