From fc5664ea5cb2442c63ba5ed22a361effea6fd1b5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 19 Jun 2025 10:59:10 +0300 Subject: [PATCH 1/8] feat: view dev guide --- sdk_dev_guide/current.md | 1008 ++++++++++++++++++++++++++------------ 1 file changed, 691 insertions(+), 317 deletions(-) diff --git a/sdk_dev_guide/current.md b/sdk_dev_guide/current.md index 7e35b01b..a9e66d11 100644 --- a/sdk_dev_guide/current.md +++ b/sdk_dev_guide/current.md @@ -890,65 +890,327 @@ end_sesson=1&session_duration=30

View Tracking

- Reporting views would allow you to analyze which views/screens/pages were visited by the app user as well as how long they spent on a specific view. If it is possible to automatically determine when a user visits a specific view in your platform, then you should provide an option to automatically track views. Also, it is important to provide a way to track views manually.  + Reporting views enables you to analyze which screens or pages were visited by + users and how much time they spent on each one, providing valuable insights into + user engagement and navigation patterns.

-

- View Structure -

+

Exposed Methods

- View information is packaged into events. There are 2 kinds of events:  + Config Methods +

+
CountlyConfig.enableAutomaticViewTracking()
+
CountlyConfig.enableAutomaticViewShortNames()
+
CountlyConfig.setAutomaticViewTrackingExclusions(exclusions: Array<String>)
+
CountlyConfig.setGlobalViewSegmentation(segmentation: Map<String, Object>)
+
CountlyConfig.disableOrientationTracking()
+

+ Instance Methods +

+
CountlyInstance.setGlobalViewSegmentation(segmentation: Map<String, Object>)
+
CountlyInstance.updateGlobalViewSegmentation(segmentation: Map<String, Object>)
+
String CountlyInstance.startAutoStoppedView(viewName: String)
+
String CountlyInstance.startAutoStoppedView(viewName: String, segmentation: Map<String, Object>)
+
String CountlyInstance.startView(viewName: String)
+
String CountlyInstance.startView(viewName: String, segmentation: Map<String, Object>)
+
CountlyInstance.stopViewWithName(viewName: String)
+
CountlyInstance.stopViewWithName(viewName: String, segmentation: Map<String, Object>)
+
CountlyInstance.stopViewWithID(viewID: String)
+
CountlyInstance.stopViewWithID(viewID: String, segmentation: Map<String, Object>)
+
CountlyInstance.pauseViewWithID(viewID: String)
+
CountlyInstance.resumeViewWithID(viewID: String)
+
CountlyInstance.stopAllViews(segmentation: Map<String, Object>)
+
CountlyInstance.addSegmentationToViewWithID(viewID: String, segmentation: Map<String, Object>)
+
CountlyInstance.addSegmentationToViewWithName(viewName: String, segmentation: Map<String, Object>)
+

Implementation Details

+

+ If it is possible to automatically determine when a user visits a specific view + in your platform, then you should provide an option to automatically track views. + Also, it is important to provide a way to track views manually. +

+

+ View tracking data is reported as events. There are two types of view events:

+

+ Both use the event key "[CLY]_view". Each event is sent with + "count": 1. Duration (in seconds) is reported via the + "dur" field. +

+

Each event includes up to four reserved segmentation keys:

+ +

Example: first view entry

+
{
+  "key": "[CLY]_view",
+  "count": 1,
+  "segmentation": {
+    "name": "view1",
+    "segment": "Android",
+    "visit": 1,
+    "start": 1
+  }
+}
+

Example: reporting view duration

+
{
+  "key": "[CLY]_view",
+  "count": 1,
+  "dur": 30,
+  "segmentation": {
+    "name": "view1",
+    "segment": "Android"  
+  }
+}

- All view-related events use the event key "[CLY]_view" and are dependent on the - consent key "views". + For details, see the + Countly View Tracking API.

-

All view events are sent with a "count" of 1.

- The duration field "dur" is used when reporting view duration. It should contain - the view duration in seconds. + For all segmentation inputs, segmentation must be validated before processing, + constraints for a segmentation:

-

There are 5 potential segmentation values:

- A sample event for reporting the first view would look like this: + View tracking can be configured through + SDK behavior settings, if it is disabled + in the Behavior Settings, all view calls must be omiited. +

+

+ For config method enableAutomaticViewTracking +

+
CountlyConfig.enableAutomaticViewTracking()
+
+// Logic
+Enables automatic tracking of views if the system supports it.
+Registers system-specific lifecycle callbacks to track view appearances automatically.
+- Like onViewStart, onViewStop, onViewPause, onViewResume
+
+

+ For config method enableAutomaticViewShortNames +

+
CountlyConfig.enableAutomaticViewShortNames()
+
+// Logic
+When enabled, view names will be shortened automatically by trimming package or path prefixes.
+Helps in reducing verbose or repetitive view naming in analytics.
Only will get class of view name. +- If view name is something like ly.count.some.View name must be shortened to View
+

+ For config method setAutomaticViewTrackingExclusions +

+
CountlyConfig.setAutomaticViewTrackingExclusions(exclusions: Array<String>)
+
+// Valid values
+Only non-empty strings accepted.
+
+// Logic
+Sets a list of view names to be excluded from automatic view tracking.
+Useful to prevent tracking of internal, debug, or unwanted views.
+- Simply omits the views if they exist in the exclusions list.
+
+

+ For config method setGlobalViewSegmentation +

+
CountlyConfig.setGlobalViewSegmentation(segmentation: Map<String, Object>)
+
+// Valid values
+Only non-empty and non-null values accepted. And, for values only mentioned validated constraints above accepted.
+
+// Logic
+Sets a global segmentation dictionary that will be attached to all views.
+Allows adding common context or user attributes globally.
+
+

+ For config method disableOrientationTracking +

+
CountlyConfig.disableOrientationTracking()
+
+// Logic
+Disables automatic tracking of device orientation changes.
+Useful to reduce noise or overhead if orientation data is not needed.
+
+

+ For instance method setGlobalViewSegmentation +

+
CountlyInstance.setGlobalViewSegmentation(segmentation: Map<String, Object>)
+
+// Valid values
+Only non-empty and non-null values accepted. And, for values only mentioned validated constraints above accepted.
+
+// Logic
+Sets a global segmentation dictionary that will be attached to all views.
+Useful to add common context or attributes to all views globally.
+
+

+ For instance method updateGlobalViewSegmentation

-
events=[
{
"key": "[CLY]_view",
"count": 1,
"segmentation": {
"name": "view1",
"segment": "Android",
"visit": 1,
"start": 1,
"_idv": "f0e8f5db5e5d9e7b9a45d3916b93e43dd091153fdfb6c9a6f"
}
}
]
+
CountlyInstance.updateGlobalViewSegmentation(segmentation: Map<String, Object>)
+
+// Valid values
+Only non-empty and non-null values accepted. And, for values only mentioned validated constraints above accepted.
+
+// Logic
+Updates the existing global segmentation by merging the new keys and values.
+Allows to add or overwrite global segmentation properties dynamically.
+
+

+ For instance method startAutoStoppedView +

+
String CountlyInstance.startAutoStoppedView(viewName: String)
+
+// Valid values
+Only non-empty values accepted
+
+// Logic
+Starts a view that will be stopped with another view.
+Returns a unique view ID for further reference.
+- If automatic view tracking enabled, this call will be omitted because manually calling this function end the automatically started views by the callbacks.
+- If view tracking is not enabled in the SDK behavior settings, call need to be omitted.
+- viewName is validated
+- Previously started automatic views must end
+- View segmentation will be created:
+  - If global view segmentation set, it is added
+  - If it is the first view in the session "start" added
+  - "visit", "segment", "name" parameters added which key length SDK internal limit is applied to the name.
+- Records the view event
+- Returns the view ID generated
+
+

+ For instance method startAutoStoppedView with segmentation +

+
String CountlyInstance.startAutoStoppedView(viewName: String, segmentation: Map<String, Object>)
+
+// Logic
+Same as startAutoStoppedView(viewName) but allows attaching segmentation data for that specific view start.
+- While creating the view segmentation, before adding reserved keys given segmentation needs to be validated and all related SDK internal limits must be applied
+- After it is validated, already validated global view segmentation need to be added and after addition segmentation values SDK internal limit must be applied
+- At last, reserved segmentation keys are added. They must not affected from the segmentation values SDK internal limit.
+
+

+ For instance method startView +

+
CountlyInstance.startView(viewName: String): String
+
+// Logic
+Explicitly starts tracking a view session with the given name without auto-stop behavior.
+Returns a unique view ID for this session.
+
+

+ For instance method startView with segmentation +

+
String CountlyInstance.startView(viewName: String, segmentation: Map<String, Object>)
+
+// Logic
+Same as startView(viewName) but allows attaching segmentation data for this view session.
+
+

+ For instance method stopViewWithName +

+
CountlyInstance.stopViewWithName(viewName: String)
+
+// Logic
+Stops tracking a view session by its name.
+If multiple sessions with the same name exist, the most recent one is stopped.
+
+

+ For instance method stopViewWithName with segmentation +

+
CountlyInstance.stopViewWithName(viewName: String, segmentation: Map<String, Object>)
+
+// Logic
+Stops the view session by name and attaches segmentation data to the stop event.
+
+

+ For instance method stopViewWithID +

+
CountlyInstance.stopViewWithID(viewID: String)
+
+// Logic
+Stops tracking a view session by its unique ID.
+
+

+ For instance method stopViewWithID with segmentation +

+
CountlyInstance.stopViewWithID(viewID: String, segmentation: Map<String, Object>)
+
+// Logic
+Stops the view session by ID and attaches segmentation data to the stop event.
+
+

+ For instance method pauseViewWithID +

+
CountlyInstance.pauseViewWithID(viewID: String)
+
+// Logic
+Pauses the timing of the view session identified by the given ID.
+Useful when the app goes into background or temporary interruptions occur.
+

- Sample event for reporting this view's duration: + For instance method resumeViewWithID

-
events=[
{
"key": "[CLY]_view",
"count": 1,
"dur": 30,
"segmentation": {
"name": "view1",
"segment": "Android",
"_idv": "f0e8f5db5e17ad5ce5cf53916b93e43dd091153fdfb6c9a6f"
}
}
]
+
CountlyInstance.resumeViewWithID(viewID: String)
+
+// Logic
+Resumes a paused view session identified by the given ID.
+
+

+ For instance method stopAllViews +

+
CountlyInstance.stopAllViews(segmentation: Map<String, Object>)
+
+// Logic
+Stops all currently active view sessions and attaches the given segmentation data to each stop event.
+

- Here is more information on view-tracking APIs. + For instance method addSegmentationToViewWithID

+
CountlyInstance.addSegmentationToViewWithID(viewID: String, segmentation: Map<String, Object>)
+
+// Logic
+Adds or updates segmentation data on an active view session identified by the given ID.
+
+

+ For instance method addSegmentationToViewWithName +

+
CountlyInstance.addSegmentationToViewWithName(viewName: String, segmentation: Map<String, Object>)
+
+// Logic
+Adds or updates segmentation data on active view sessions matching the given name.
+

View Manual Reporting

@@ -1648,17 +1910,21 @@ end_sesson=1&session_duration=30 It should replace the internally used device ID with the new one, and use it for all new requests, persistently storing it for further sessions. The Countly SDK should follow these steps:

Changing ID With Merging @@ -1667,18 +1933,22 @@ end_sesson=1&session_duration=30 Developers may need to change a device ID to their own internal user ID and merge the server-side data previously generated by a user while he/she was unauthenticated. It is similar to "Changing ID without merging", but the Countly SDK will need to merge the data on the server as well. In order to make a proper transition, the Countly SDK should follow these steps:

Retrieving the Current Device ID and Type

@@ -1719,9 +1989,13 @@ end_sesson=1&session_duration=30 The currently used ones are the following:

Platform Specific Notes

Additional Intent Redirection Checks (Android)

@@ -1748,10 +2022,14 @@ end_sesson=1&session_duration=30 can include:

Product Information: @@ -2301,15 +2579,19 @@ Countly.heatmap_whitelist = ["https://you.domain1.com", "https://you.domain2.com After a user gives a rating, a reserved event will be recorded with [CLY]_star_ratingas the key and following as the segmentation dictionary:

If a user dismisses the star-rating dialog without giving a rating, an event will not be recorded. The star-rating dialog's message and dismiss button title may be customized using the properties on the initial configuration object. @@ -2375,27 +2657,31 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title "recordRatingWidgetWithID" should record an event with the internal key "[CLY]_star_rating". The event should have the following segmentation:

Basic filtering (type checks) on the provided values should be performed. Mandatory values must be provided. Invalid widget ID's (non string or empty values) should not be accepted. Rating value should be modified, if necessary, so that it lies within the acceptable range of [1,5]. @@ -2414,18 +2700,23 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title The Feedback Widgets API provides access to three types of widgets:

They are shown using a very similar server API and basically the same processing. @@ -2436,27 +2727,32 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title

Feedback widgets can be used through three methods:

Retrieving the List of Eligible Widgets

@@ -2569,13 +2865,17 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title

Automatic Feedback Widgets

Automatic Feedback Widget reporting has 3 steps:

-
    -
  1. Retrieve a list of available widgets and pick one.
  2. -
  3. - Presenting the widget with presentFeedbackWidget call. -
  4. -
  5. Handling the callbacks.
  6. -
+

As explained in the 'Retrieving the List of Eligible Widgets' section, the first step uses the getAvailableFeedbackWidgets function to communicate @@ -2593,40 +2893,49 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title It should be possible to provide 2 optional callbacks to the presentFeedbackWidget call:

-
    -
  1. - a callback (potentially named widgetShown) that is called when - the widget is successfully presented (currently that would mean that there - were no issues/errors while trying to show the dialog with the WebView). - Also, we aren't verifying if the WebView is showing a working widget. If - there are any issues during the display of the widget, this callback will - return an error message. -
  2. -
  3. - a callback (potentially named widgetClosed) that is called when - the widget/dialog is closed (for mobile SDKs and for the web SDK that would - mean slightly different things, but the main point is to have the host app/site - notified of when the "feedback process" is over). If there are any issues - during the closing of the widget, this callback will return an error message. -
  4. -
+

Manual Feedback Widgets

Manual feedback widget reporting has 3 steps:

-
    -
  1. - Retrieve a list of available widgets and pick one. This is the same initial - step with the automatic feedback widgets and reuses the same call to retrieve - them. -
  2. -
  3. - Download widget data from the server (for that single widget with the information - that has been retrieved from the previous step). -
  4. -
  5. - Report the feedback result (for that single widget according to that data - from the previous step). -
  6. -
+

As mentioned before, the first step uses getAvailableFeedbackWidgets function, which is also used for automatic @@ -2696,15 +3005,19 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title to the type of the reported widget. That event should have the following segmentation:

In addition to this segmentation which identifies the widget, the contents of @@ -2714,9 +3027,13 @@ CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title value:

After this event has been added to the event queue, the event queue should be @@ -2788,8 +3105,12 @@ string constructFeedbackWidgetUrl(CountlyFeedbackWidget chosenWidget);

Additionally, there could be custom key values added to the user details. In this case, you would need to provide a means to set them:

You may find more information on what data may be set for a user by following this link. @@ -2811,17 +3132,21 @@ string constructFeedbackWidgetUrl(CountlyFeedbackWidget chosenWidget);

The standard methods that should be provided by the SDK are as follows (provided as pseudo-code, naming conventions may differ from platform to platform):

Notewhen reporting to the server, assure the push, pushUnique, and pull parameters can provide multiple values for the same property as an array. @@ -2856,9 +3181,13 @@ string constructFeedbackWidgetUrl(CountlyFeedbackWidget chosenWidget);

Trace / Metric keys

@@ -2918,14 +3247,18 @@ string constructFeedbackWidgetUrl(CountlyFeedbackWidget chosenWidget);

Consent management in the SDK is done in 2 steps

-
    -
  1. - consent has to first be required in the app otherwise, the SDK works as if all consent is given -
  2. -
  3. - if consent is required, it has to explicitly be given for each targeted feature -
  4. -
+

Initial Configuration

During SDK init there should be a flag (e.g. requiresConsent) to @@ -2950,67 +3283,71 @@ string constructFeedbackWidgetUrl(CountlyFeedbackWidget chosenWidget);

The following are the currently available features:

Note that the available features may change depending on the platform. @@ -3246,10 +3583,15 @@ string constructFeedbackWidgetUrl(CountlyFeedbackWidget chosenWidget);

&aid={"rndid":[SOME_OTHER_ID_VALUE]}

SDK Internal Limits

The SDK should have the following limits:

-