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

 

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

Looking for help?