A deeper look at SDK concepts

Follow

Sessions

Session in its most basic definition is a group of interactions a user engages in your application/website in a given timeframe. It can be used to keep track of user specific state like user identity, views and events. Again, it can be seen as hits to the server by a single user, grouped in a certain way. Countly has a specific internal logic to group these hits and calls them as sessions.

In Countly, there are two main methods you can use to track sessions in your application or website. These are namely  automatic session tracking and manual session tracking. They both obey the same session flow where a session starts with a “begin session” signal, a session is updated/extended with a “session duration” signal (which is configurable and by default it is every 60 seconds) and where a session end mark is given by “end session” signal. While the automatic session tracking follows this flow with a certain logic and triggers, as explained below, in the case of the manual tracking the triggers are set by the developers who integrate the SDK to their application or website.

In automatic session tracking, sessions always start with a “begins session” signal when a user connects to your website/app, session is updated/extended by “session update” signal as long as the user is active and the session ends with an “end session” request when the app or site is closed.

However for Web SDK, after the session has started, as long as the user is active (like clicking or scrolling through a page), the inactivity timer will be reset to its initial value (which is 20 minutes by default). This way the inactivity timer will only start counting when a user is inactive for the last minute (this default value can be changed during the initialization.). And if the user stays inactive for the whole duration of the inactivity timer, only then, the session will come to an end.

In case a user becomes active just at the end of the inactivity timer or just at the end of the session, Countly servers provide a grace period to extend the session instead of terminating it. This “session cooldown” value is 15 seconds by default and can be changed from the Countly dashboard under the settings section:

001.png

So if a user becomes active from inactivity at the end of inactivity timer or opens your site/app again after closing it, as long as it is within the session cooldown time, the user session will be extended instead of the creation of a new session.

Another option you can change from your dashboard’s settings section is the “maximal session duration”:

002.png

This is a limit for your session update signals, where, if you were updating your session in a longer time frame than this value (which is 2 minutes by default) Countly servers would cap them to this default value instead to provide a better session flow logic, as tandem to the session update signal, the SDK also sends recorded events until that time as a request to the server and these events are tied to the begin session signal coming before them.

Session information of a user can be observed from their user profile under the “session history” section with their corresponding events, duration and starting time:

003.png

 

So the total duration of a session will be calculated by summing up these session duration signals and these signals would only be sent as long as the user is active or if not, at least inactive while keeping the app/site open within the inactivity timer’s activity, for automatic session tracking.

Manual session tracking works on the same principle as the automatic tracking however the developer has the total control over when and where to send “begin session”, “session update” and “end session” signals. Also other inner logic that is ingrained into automatic session tracking must be handled by the developer in order to achieve a proper integration. Which are:

  • Sending metrics and location information with the begins session signal

  • Sending begin session only once per app use/ site visit

  • Handling inactivity timer logic (optional, for web sdk)

  • Sending session update signals with time elapsed regularly (with an internal logic)

  • Sending end session signal at proper occasion like tab or app close

User metrics and location information should also be sent with the begin session signal. Things like inactivity timer and other time related matters have to be handled by the developer to record accurate session information, considering the limitations of the platform that they are working with and keeping in mind the behavior and the expectations of the Countly server.

It should also be kept in mind that, if consents are enabled during the initialization, if “session” consent was not provided, session tracking would not be working.

Reporting "feature data" manually with events

Views

Currently, SDK doesn't have any direct mechanism to record views. You may record views by using RecordEvent method. 

There are a couple of other values that can be set when recording a view. 

  • key- [CLY]_view is a predefined by SDK, do not change it while recoding a view.
  • segmentation - A map where you can provide view's name other information related to views.  You may also provide custom data for your view to track additional information. It is a mandatory field, you may not set it to null.
  • count - It defines how many time this event occurred. Set it to 1.

Example:

std::map<std::string, std::string> segmentation;
segmentation["name"] = "view-name";
segmentation["visit"] = "1";
segmentation["segment"] = "Windows";
segmentation["start"] = "1";

Countly::getInstance().RecordEvent("[CLY]_view", segmentation, 1);

Note: 'name', 'visit', 'start' and 'segment' are internal keys to record a view.

Reporting a feedback widget manually

This guide will go into the reporting of feedback widgets (nps, surveys and ratings) manually. It will give more context into how the widget data should be interpreted and how the response should be structured when reporting back to the SDK. Also it must be noted that not all SDKs contain the functionality to do manual feedback widget reporting. 

The SDK should provide 3 calls to perform this process:

  1. A call to fetch the widget list from the server
  2. A call to fetch a single widget's data from the server
  3. A call to report a single widget to the server

Widget list received would be a JSON array of objects corresponding to the widgets. By selecting one of these objects (widgets) and providing it in the second call developer can fetch its data from the server. When receiving the data, it would be packaged in a JSON type object. Their structure would slightly differ depending on the type of widget being reported.

In case of a survey, widget data would look something like this:

{
"_id":"601345cf5e313f747656c241",
"app_id":"5e3356e07b96b63120334842",
"name":"Survey name",
"questions":[
{
"type":"multi",
"question":"Multi answer question",
"required":true,
"choices":[
{
"key":"ch1611875792-0",
"value":"Choice A"
},
{
"key":"ch1611875792-1",
"value":"Choice B"
},
{
"key":"ch1611875792-2",
"value":"Choice C"
},
{
"key":"ch1611875792-3",
"value":"Choice D"
}
],
"randomize":false,
"id":"1611875792-0"
},
{
"type":"radio",
"question":"Radio button question",
"required":false,
"choices":[
{
"key":"ch1611875792-0",
"value":"First"
},
{
"key":"ch1611875792-1",
"value":"Second"
},
{
"key":"ch1611875792-2",
"value":"Third"
},
{
"key":"ch1611875792-3",
"value":"Fourth"
}
],
"randomize":false,
"id":"1611875792-1"
},
{
"type":"text",
"question":"Text input question",
"required":true,
"id":"1611875792-2"
},
{
"type":"dropdown",
"question":"Question with a dropdown",
"required":false,
"choices":[
{
"key":"ch1611875792-0",
"value":"Value 1"
},
{
"key":"ch1611875792-1",
"value":"Value 2"
},
{
"key":"ch1611875792-2",
"value":"Value 3"
}
],
"randomize":false,
"id":"1611875792-3"
},
{
"type":"rating",
"question":"Rating type question",
"required":false,
"id":"1611875792-4"
}
],
"msg":{
"thanks":"Thanks for your feedback!"
},
"appearance":{
"show":"uClose",
"position":"bLeft",
"color":"#2eb52b"
},
"type":"survey"
}

 

In case of a NPS widget, the JSON internally would look something like this:

{
   "_id":"60186d8b3687037dbb058d80",
   "app_id":"5e3356e07b96b63120334842",
   "name":"test3",
   "msg":{
      "mainQuestion":"How likely are you to recommend this product to a friend?",
      "followUpAll":"",
      "followUpPromoter":"We're glad you like us. What do you like the most about our product?",
      "followUpPassive":"Thank you for your feedback. How can we improve your experience?",
      "followUpDetractor":"We're sorry to hear it. What would you like us to improve on?",
      "thanks":"Thanks for your feedback!"
   },
   "followUpType":"score",
   "appearance":{
      "show":"uSubmit",
      "color":"#027aff",
      "style":"full"
   },
   "type":"nps"
}

And incase of a rating widget it would look something like this:

{
"_id":"62222d125852e20462481193",
"popup_header_text":"What&#39;s your opinion about this page?",
"popup_comment_callout":"Add comment",
"popup_email_callout":"Contact me via e-mail",
"popup_button_callout":"Submit feedback",
"popup_thanks_message":"Thank you for your feedback",
"trigger_position":"mright",
"trigger_bg_color":"13B94D",
"trigger_font_color":"FFFFFF",
"trigger_button_text":"Feedback",
"target_devices":{
"phone":true,
"desktop":true,
"tablet":true
},
"target_page":"all",
"target_pages":["/"],
"is_active":"true",
"hide_sticker":false,
"app_id":"12345687af5c256b91a6345f",
"contact_enable":"true",
"comment_enable":"true",
"trigger_size":"m",
"type":"rating",
"ratings_texts":[
"Very dissatisfied",
"Somewhat dissatisfied",
"Neither satisfied Nor Dissatisfied",
"Somewhat Satisfied",
"Very Satisfied"
],
"status":true,
"targeting":null,
"ratingsCount":116,
"ratingsSum":334
}

These describe all server side configured information that would be used to visualize a widget manually. Starting from some style and color related fields and finally all questions and their potential answers. In the case of surveys, it also shows the required id's to report survey results.

 

When reporting these widget's results manually, the filled out response is reported through the segmentation field of the reporting event. So depending on the type of widget you are reporting, you have to construct a widgetResult object, specific to that widget, which would then be utilized in the third call that has been mentioned at the top of this section. In this third call the developer is expected to provide the widget object obtained from the first call, the widget's data object that has been obtained from the second call, and a properly formed widgetResult object that has been created with respect to the type of widget that is being reported. More information on how to form this object is provided below.

Reporting NPS widgets manually

To report the results of an NPS widget manually, no information from the widget's data JSON is needed. These widgets can report only two pieces of information - an integer rating, ranging from 0 to 10, and a String comment, representing the user's comment.

Therefore when reporting these results, you need to set two segmentation values, one with the key of "rating" and an int value and the other with the "comment" key and a String value.

Android sample code

The following sample code would report the result of an NPS widget:

Countly.sharedInstance().feedback().getFeedbackWidgetData(chosenWidget, new RetrieveFeedbackWidgetData() {
@Override public void onFinished(JSONObject retrievedWidgetData, String error) {
Map<String, Object> segm = new HashMap<>();
segm.put("rating", 3);//value from 0 to 10
segm.put("comment", "Filled out comment");

Countly.sharedInstance().feedback().reportFeedbackWidgetManually(widgetToReport, retrievedWidgetData, segm);
}
});

Web sample code

The following code shows what is the expected widgetResult objects looks like for NPS widget:

var widgetResult = {
rating: 3, // between 0 to 10
comment: "any comment" // string
};

Reporting Rating widgets manually

To report the results of a Rating widget manually, again no information from the obtained widget data is needed. These widgets has similar reporting capabilities to NPS widgets. So similarly there would be an integer rating, ranging from 1 to 5, and a String comment, representing the user's comment. Then in addition to these there would be an email String for user email and a contactMe Boolean (true or false) if the user gave consent to be contacted again or not.

Web sample code

The following code shows what is the expected widgetResult objects looks like for Rating widget:

var widgetResult = {
rating: 3, // between 1 to 5
comment: "any comment", // string
email: "email@any.mail", // string
contactMe: true // boolean
};

Reporting Survey widgets manually

To report survey widgets manually, investigation of the widget data received from the second call is needed. Each question has a question type and depending on the question type, the answer needs to be reported in a different way. Each questions also has it's own ID which needs to be used as part of the segmentation key when reporting the result.

The questions id can be seen in the "id" field. For example, in the above posted survey JSON the first question has the ID of "1611875792-0". When trying to report the result for a question, you would append "answ-" to the start of an ID and then use that as segmentation key. For example, for the first survey questions you would have the result segmentation key of "answ-1611875792-0".

At the end your widgetResult would look something like this (Web example):

var widgetResult = {
"answ-1602694029-0": "answer", // for text input fields
"answ-1602694029-1": 7, // for rating picker
"answ-1602694029-2": "ch1602694029-0", // there is a question with choices. It is a choice key
"answ-1602694029-3": "ch1602694030-0,ch1602694030-1" // in case 2 choices selected
};

The specific value would depend on the question type. Here is a description of how to report results for different question types:

Multiple answer question

It has the type "multi". In the question description there is a field "choices" which describes all valid options and their keys.

Users can select any combination of all answers.

You would prepare the segmentation value by concatenating the keys of the chosen options and using a comma as the delimiter.

For example, the above survey has 4 options in it's first question. If a user would choose the first, third and fourth option as the result, the resulting value for the answer would be: "ch1611875792-0,ch1611875792-2,ch1611875792-3".

Radio buttons

It has the type "radio". In the question description there is a field "choices" which describes all valid options and their keys.

Only one option can be selected.

You would use the chosen options key value as the value for your result segmentation.

Dropdown value selector

It has the type "dropdown". In the question description there is a field "choices" which describes all valid options and their keys.

Only one option can be selected.

You would use the chosen options key value as the value for your result segmentation.

Text input field

It has the type "text".

You would provide any string you want as the answer.

Rating picker

It has the type "rating"

You would provide any int value from 1 to 10 as the answer.

Android sample code

The following sample code would go through all of the received Survey widgets questions and choose a random answer to every question. It the reports the results:

Countly.sharedInstance().feedback().getFeedbackWidgetData(chosenWidget, new RetrieveFeedbackWidgetData() {
@Override public void onFinished(JSONObject retrievedWidgetData, String error) {
JSONArray questions = retrievedWidgetData.optJSONArray("questions");

Map<String, Object> segm = new HashMap<>();
Random rnd = new Random();

//iterate over all questions and set random answers
for (int a = 0; a < questions.length(); a++) {
JSONObject question = null;
try {
question = questions.getJSONObject(a);
} catch (JSONException e) {
e.printStackTrace();
}
String wType = question.optString("type");
String questionId = question.optString("id");
String answerKey = "answ-" + questionId;
JSONArray choices = question.optJSONArray("choices");

switch (wType) {
//multiple answer question
case "multi":
StringBuilder sb = new StringBuilder();

for (int b = 0; b < choices.length(); b++) {
if (b % 2 == 0) {//pick every other choice
if (b != 0) {
sb.append(",");
}
sb.append(choices.optJSONObject(b).optString("key"));
}
}
segm.put(answerKey, sb.toString());
break;
//radio buttons
case "radio":
//dropdown value selector
case "dropdown":
int pick = rnd.nextInt(choices.length());
segm.put(answerKey, choices.optJSONObject(pick).optString("key"));//pick the key of random choice
break;
//text input field
case "text":
segm.put(answerKey, "Some random text");
break;
//rating picker
case "rating":
segm.put(answerKey, rnd.nextInt(11));//put a random rating
break;
}
}

Countly.sharedInstance().feedback().reportFeedbackWidgetManually(widgetToReport, retrievedWidgetData, segm);
}
});

There Is No SDK That I Can Integrate for My Use Case, What Can I Do?

Countly SDKs provide you with many options to track your users with the least amount of code in a way that fits your use case. Behind the scene, the SDK would do various tasks to gather information, reshape this information in a way the Countly servers can understand, and prevent the possibility of data loss as much as possible. With the help of the SDKs, you only need to write a single line of code while the SDK does hundreds of lines of work behind the hood. However, the core principles behind these operations are simple even though they are tedious. So if you come across a situation where you can not integrate a Countly SDK into your project, you can still be able to track your users and inform that information to your Countly instance as long as you can send API calls following the core rules and structures that is shared among all Countly SDKs.

To be able to track your users manually and to share this information you have gathered with your Countly instance you will need to know three things:

  1. What information is the server looking for?
  2. What API endpoint you should use?
  3. How should you structure your requests?

As long as you have the answers to these questions, you can track your users and gather information in any way you want as long as you form and send correct requests to your Countly server. Documentations that would be useful to find the answers to these questions are the Countly glossary to understand the Countly terminology, the API documentation to see the endpoints and the data structure, the SDK Development Guide to see the scope of the endpoints, and the specific documentation of the SDK of your platform to see the capabilities and the features.

Handling the Device ID in Your Integrations

Countly tracks your users through an ID called the 'device ID'. This is attached to every request (which contain events and other data) that is sent to the Countly server. This ID consists of String characters.

This ID is normally (by default) generated in the environment the Countly SDK has been integrated into (e.g. a smartphone, a web browser, or a desktop application). But how you handle this ID would depend on how you define a user in your platform specifically.

What happens if there are several users and several devices that are used interchangeably? What happens if a user can log in and log out, hence transitioning between a known and an anonymous user? In such cases, you should experiment and decide on the correct user tracking strategy before going into production to minimize the negative effects. For an overview on how these different situations could be handled, look bellow.

Available Mechanisms For Interacting With Device ID

Countly SDK's try to be configurable and flexible, and handling device ID's is no exception.

Device ID During Init

Countly SDK's behave differently in the first on a device compared to subsequent init's.

On your first init, when integrating a Countly SDK, Countly will try to acquire a device ID. By default, Countly will generate a random value (for the device_id) to identify the user or use some platform-specific value; for example, IDFV for iOS, and then store it in the local storage.

On subsequent inits, the SDK would fetch and use this same value as the device ID and would not generate a new one. By default, the SDK would ignore any provided device ID values.

There are some configuration options during initialization. During the first init, it is possible to do the following actions:

  • provide a custom device ID - SDK will use the provided ID and will not generate one
  • tell the SDK which device ID generation method to use - in some SDK's it is possible to influence the ID generator and pick a specific method
  • enable temporary device ID mode - while in this mode, the SDK will not send anything to the countly server until a device ID has been provided
  • provide a device ID with a url parameter - this exists only in the web SDK. This provides a way to "inject" a device ID on a first run.

Some SDK's might have a "clear stored device ID" flag that can be set during init. If this is done then the SDK will clear it's stored value and will try to reacquire a device ID value and would behave like on the first init. It is generally not advised to use this flag as it can cause user count inflation issues.

For a deeper overview in how the SDK would behave in different situations, have a look at this table.

Changing Device ID

Countly SDK's provide two ways to change the device_id after the SDK initialization:

1. Change device_id without merging. That will simply end the session of the old device_id, sync all the left data, and start a new session for the new device_id.

This is handy, for example, when multiple users use the same device and you want to track them without sharing their data individually.

2. Change device_id with merging. This will create a new user with a new device_id, and start a new session. Then, merge the data of the anonymous user with the old device_id into this new ID. And afterward, delete the anonymous user with the old device_id from Countly and only keep this user's information under the new, developer given ID.

This is handy when, for example, you are firstly tracking an anonymous user with a Countly generated device_id, but then the user authenticates so you retrieve the ID for this user and change it in the SDK, allowing to merge both users on the server. This means that everything that the anonymous user had, all events and properties, will now be assigned to an identified user and the old user will be deleted.

You can implement different strategies that utilize these two options with the help of device ID type information. Most Countly SDKs provide calls to see the current device ID and the device ID type. The main types you would like to check for device ID management are to see if the ID was SDK generated or developer supplied.

Offline / Temporary ID mode

It is possible to launch the Countly SDK in an offline/temporary ID mode during the first initialization. This  mode can also enabled after initialization with the use of special calls exposed by the SDK.

If this mode is enabled, no data will be sent to the server until a real device ID value is provided by the host app. After that is done, all stored requests will be marked as create by this device ID and then sent to the server and assigned to this user.

Device ID Type

Most Countly SDKs provide calls to see the current device ID and the device ID type. The main types you would like to check for device ID management are to see if the ID was SDK generated or developer supplied.

Different user tracking strategies

Default User Tracking

Like mentioned in the "Device ID during init" section. With no additional configuration, the SDK will generate a random device ID on the first init and then use it.

Generally speaking, this way assigns a unique ID to a particular user who is the owner of that device (phone, browser, PC, or tablet)

Pros

  • This is the easiest and fastest implementation, with no additional steps needed other than undertaking the default SDK implementation.

Cons

  • If multiple different users use the same device, they will be identified as a single user in the Countly dashboard and will have a single profile under User Profiles.
  • If the same user uses multiple devices, each device will be identified as a separate user in the Countly dashboard; hence the same user will have separate user profiles.
  • Depending on the platform, if app storage is reset (erased) or the app is uninstalled and re-installed again, this user most likely will be identified as a new user and a new user profile is created. This highly depends on how the platform behaves. Check here to understand what happens in such cases.

Tracking Known Users

This method, as opposed to the first one, helps Countly identify and track users if they are known to you. It is used when tracking the same user across multiple devices or different users on the same device, as the default tracking method is not appropriate. In this case, you need to provide your user identifier as the device ID. This unique identifier can be a user email address or an internal customer ID — or simply anything unique to that user. The Countly SDK can then use this string as the device_id. From this point on, Countly will know precisely what user it is and the same device_id will be used even across different devices.

To accomplish that, you need to provide a string value as the device_id upon the SDK initialization inside the config object. The user authentication page is a good candidate for implementing this method. So this might fit applications that can identify their users right away during the SDK init, or have little or no actions before authenticating users.

This would be the case when the user inside the Countly dashboard directly corresponds to your customer (e.g. 1 Countly user = 1 company customer, regardless of the device or platform they use).

Pros

  • Each of your customers will be exactly 1 single user inside Countly and have 1 user profile.

Cons

  • If you do not know your user ID right away and would know it only after the user authenticates, you would miss all the actions that were made before authentication.

Known User With Pre-Tracking

To tackle the problem of missing out on data before user authentication, it is possible to launch the Countly SDK in an offline/temporary ID mode. This mode is described in the Offline / Temporary ID mode section.

This way, you can track everything needed before knowing the users identity. When the user finally authenticates, and you get your user’s identifier, and use that to exit the Offline / Temporary ID mode

For the definition of the user, nothing changes - it still directly corresponds to your customer.

Pros

  • Each of your customers will be exactly 1 single user inside Countly and have 1 user profile.
  • You will have the opportunity to be able to collect and visualize data before the user authenticates, but only after authentication.

Cons

  • If your user does not authenticate (and so be known), you will never receive any data from this user.

Managing Anonymous and Known Users Together

It is also possible to collect data of both user states (before login/known and after login/known) and manage the ID using the functionality discussed in the above changing device ID section.

You can implement different strategies that utilize these two options with the help of device ID type information discussed device ID type section.

So with this knowledge, for example, you can start tracking a user as anonymous with a Countly generated ID. Then, upon authentication, change the device_id to your own ID by merging. And then, when the user logs out, you can change it back to the anonymous generated ID without user merge. The problem is that when the user logs out, it will create a new user inside the Countly dashboard again, and there will be 2 different user profiles: one with your provided ID and the other with a random ID.

So the user on the Countly dashboard represents both your customer and anonymous user before authentication. And in some cases, it could be the same user but with 2 different user profiles inside Countly. For applications like banking, where the user must log in and log out every time, that can double the user count, thus skewing the data.

You can try to tweak this strategy to minimize double-user creation. For example, upon logout, let’s not change the device_id at all and keep using your provided one. Instead, upon authentication, we check if the type of device_id provided is yours, if it is we switch the device_id without merging. But if the type of device_id is Countly generated, then we change the device_id with user merging.

In such a case, the scenario would look like this:

  • The app starts for the first time and a Countly-generated ID is created.
  • Upon authentication, you confirm that the current device_id is Countly generated; it means we need to do merging when switching to your provided device_id.
  • When the user logs out, we do not do anything.
  • When the user logs in, we check the current device_id and we see that it was provided by you. So we switch to the authenticated user’s device_id without merging. If it is the same user and the same ID the SDK currently has, nothing will happen. But if it is a different ID, then it is probable that another user logged in and the SDK will stop the current session and start a new one for the new user.

These are just a couple of examples of how you could manage tracking data for both known users and anonymous ones. The actual implementations may differ based on your application specifics. But an example integration of the mentioned method can be reached from here

Pros

  • You get to track data for users both before and after authentication.

Cons

  • In some cases, aggregated data may be skewed and may over-report users and new users due to many anonymous users getting created.
  • Merging can be quite a performance-intensive process, especially if the user that is merged has a lot of data or there are lots of users to merge.
  • In some cases, the same user may have a user profile for both states: a known user and an anonymous one.
  • It requires SDK integration and customization which is slightly more difficult.

Other Known Strategies

We have seen our customers using their own different implementations, and one of them was quite effective, which is why we have included it here. This strategy involves dividing the onboarding (pre-authenticated users) and authenticated users into separate Countly apps.

In this scenario, the customer had quite a long and complicated onboarding process with the registration form. But once that was done, there was nothing else to do before the login screen. So they wanted to utilize 1 user profile per 1 customer inside the Countly dashboard. But they also wanted to track how a user onboards, how long it takes, and where they would drop off if registration was not finished.

That is why sending onboarding data to one app and then sending data of known users to the other app made perfect sense for them.

Another strategy could be you also leaving a custom property with your user ID once registration is complete on the onboarding app, just to be able to tie both users together. But note, this approach would require changing app_key in the running SDK, and currently not all SDKs support that. You would need to consult Countly or make modifications yourself on certain SDKs.

Conclusion

There are different user tracking strategies available. Each one has its own pros and cons. You need to understand what kind of data you want to collect and what you want the word user to mean exactly for you in the Countly dashboard. Make sure you know the options and then you would be able to find the best way that fits you with all its trade-offs.

Was this article helpful?
0 out of 0 found this helpful

Looking for help?