Skip to content

SDK-5763: [Android Core SDK] Add DisplayUnitCache interface + setter on CleverTapAPI#999

Merged
piyush-kukadiya merged 6 commits into
developfrom
task/SDK-5763-display-unit-cache-interface
May 19, 2026
Merged

SDK-5763: [Android Core SDK] Add DisplayUnitCache interface + setter on CleverTapAPI#999
piyush-kukadiya merged 6 commits into
developfrom
task/SDK-5763-display-unit-cache-interface

Conversation

@CTLalit
Copy link
Copy Markdown
Collaborator

@CTLalit CTLalit commented Apr 30, 2026

Jira

SDK-5763 — child of epic SDK-5256 (Native Display v2).
Companion PRs: iOS Core SDK SDK-5764 and Native Display SDK SDK-5765.

Summary

Extracts CTDisplayUnitController's read+update surface into a public DisplayUnitCache interface and exposes CleverTapAPI.setDisplayUnitCache(...) so external SDKs (Native Display) can install their own cache implementation.

Why: ND SDK's manual JSON mode feeds units the Core SDK never received from its server pipeline, so pushDisplayUnit*EventForID's mandatory cache lookup misses and analytics are unattributed. pushEvent("Notification Viewed", ...) is not a workaround — those names are in DEFAULT_RESTRICTED_EVENT_NAMES and dropped at validation. Letting ND SDK plug its own cache implementation lets attribution events resolve through the bridge's existing cache.

Changes

File Change
displayunits/DisplayUnitCache.java new public interface (getAllDisplayUnits, getDisplayUnitForID, updateDisplayUnits, reset)
displayunits/CTDisplayUnitController.java implements DisplayUnitCache (no behaviour change)
ControllerManager.java holds DisplayUnitCache reference; getDisplayUnitCache()/setDisplayUnitCache(); deprecated getCTDisplayUnitController()/setCTDisplayUnitController() retained as adapters
CleverTapAPI.java new public setDisplayUnitCache(DisplayUnitCache); getDisplayUnitForId/getAllDisplayUnits route via interface
AnalyticsManager.java pushDisplayUnit*EventForID resolve via getDisplayUnitCache()
response/DisplayUnitResponse.java server-response handler routes via interface
login/LoginController.java logout reset routes via interface
test/.../AnalyticsManagerTest.kt mock points updated to displayUnitCache

Backwards compatibility

  • Hosts that don't call the new setter: identical behaviour. The default CTDisplayUnitController is lazy-installed on first adUnit_notifs response (same lazy-init point as before, just routed via the new accessor).
  • Deprecated getCTDisplayUnitController() returns the cast default impl when present, null when a custom impl is installed.
  • DisplayUnitListener continues to fire only for server-pipeline activity.

Test plan

  • :clevertap-core:compileDebugJavaWithJavac clean
  • Full :clevertap-core:testDebugUnitTest suite passes
  • Reviewer: confirm a host that ignores the new setter sees no behaviour change
  • Reviewer: validate javadoc on new public types reads cleanly

Summary by CodeRabbit

  • New Features

    • Public API to supply/clear a display-unit cache.
    • New API to report native display "element clicked" events with optional properties.
  • Improvements

    • Display-unit lookups, resets and event tracking now route through the supplied cache; server updates populate the cache and drive listener firing.
    • Element-click events merge element ID and caller properties, preserving cached display-unit metadata while stripping reserved keys.
    • Default controller implements the cache contract and new update semantics.
  • Tests

    • Added tests for element-click merging, reserved-key stripping, metadata handling, cache-null and cache-update cases.
  • Deprecations

    • Legacy controller paths redirected toward the cache-based API.

Review Change Stack

@francispereira
Copy link
Copy Markdown

francispereira commented Apr 30, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replace direct CTDisplayUnitController usage with a host-installable DisplayUnitCache abstraction, make the SDK hold a nullable cache in ControllerManager, add CleverTapAPI#setDisplayUnitCache and element-click APIs, adapt server parsing/reset/event pushers to use the cache, and update tests and mocks to exercise the new flows.

Changes

Display Unit Cache Migration

Layer / File(s) Summary
Data shape / Contract
clevertap-core/src/main/java/com/clevertap/android/sdk/displayunits/DisplayUnitCache.java
Adds DisplayUnitCache interface exposing getAllDisplayUnits(), getDisplayUnitForID(@nullable), updateDisplayUnits(@nullable List<CleverTapDisplayUnit>), and reset().
Manager Wiring
clevertap-core/src/main/java/com/clevertap/android/sdk/ControllerManager.java
Replaces internal CTDisplayUnitController field with nullable DisplayUnitCache displayUnitCache; adds getDisplayUnitCache()/setDisplayUnitCache(); deprecated CT getters/setters now conditionally return/delegate based on the cache instance.
Analytics contract & implementation
clevertap-core/src/main/java/com/clevertap/android/sdk/BaseAnalyticsManager.java, clevertap-core/src/main/java/com/clevertap/android/sdk/AnalyticsManager.java
Adds abstract pushDisplayUnitElementClickedEventForID(...) to BaseAnalyticsManager and implements it in AnalyticsManager; AnalyticsManager and existing clicked/viewed flows now resolve units via controllerManager.getDisplayUnitCache() and include new element-click merging/filtering logic.
Public API & Lookups
clevertap-core/src/main/java/com/clevertap/android/sdk/CleverTapAPI.java
getAllDisplayUnits() and getDisplayUnitForId(...) read from coreState.getControllerManager().getDisplayUnitCache() when present; new setDisplayUnitCache(@nullable DisplayUnitCache) and pushDisplayUnitElementClickedEventForID(...) APIs added.
Response parsing & reset
clevertap-core/src/main/java/com/clevertap/android/sdk/response/DisplayUnitResponse.java, clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginController.java
DisplayUnitResponse lazily initializes/uses the DisplayUnitCache under the existing lock, parses messages into a filtered list and calls cache.updateDisplayUnits(...); LoginController.resetDisplayUnits calls controllerManager.getDisplayUnitCache().reset() when present.
Tests & Mock
clevertap-core/src/test/java/com/clevertap/android/sdk/AnalyticsManagerTest.kt, clevertap-core/src/test/java/com/clevertap/android/sdk/MockAnalyticsManager.kt, clevertap-core/src/test/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitControllerTest.kt
Tests updated to stub controllerManager.displayUnitCache (or null) instead of deprecated controller; new tests for pushDisplayUnitElementClickedEventForID validate merging, wzrk_ filtering, coreMetaData population, and null-cache behavior; MockAnalyticsManager adds a no-op override.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Reviewed chained PRs

Suggested reviewers

  • piyush-kukadiya
  • deeksha-rgb
  • darshanclevertap
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding a DisplayUnitCache interface and exposing it via a setter on CleverTapAPI, which aligns with the core changes across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/SDK-5763-display-unit-cache-interface

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginController.java (1)

293-298: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Capture the cache once before resetting it.

Line 293 calls the getter twice, so a concurrent setter can swap the cache between the null-check and reset(). That can skip the logout reset or throw if the second lookup returns null.

🔧 Proposed fix
+import com.clevertap.android.sdk.displayunits.DisplayUnitCache;
...
-        if (controllerManager.getDisplayUnitCache() != null) {
-            controllerManager.getDisplayUnitCache().reset();
+        DisplayUnitCache cache = controllerManager.getDisplayUnitCache();
+        if (cache != null) {
+            cache.reset();
         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginController.java`
around lines 293 - 298, In LoginController, avoid calling
controllerManager.getDisplayUnitCache() twice: capture its result into a local
variable (e.g., displayUnitCache) before the null check, then call
displayUnitCache.reset() if non-null; otherwise call config.getLogger().verbose
with the Constants.FEATURE_DISPLAY_UNIT message. This prevents a race where the
cache can be swapped between the null-check and reset, which could skip reset or
throw a NPE.
clevertap-core/src/main/java/com/clevertap/android/sdk/AnalyticsManager.java (1)

204-216: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reuse the same cache reference in both event paths.

Lines 204-206 and 236-238 repeat the same TOCTOU pattern. If the cache is swapped or cleared between the null-check and getDisplayUnitForID(), the event can be dropped or resolved against a different instance.

🔧 Proposed fix
+import com.clevertap.android.sdk.displayunits.DisplayUnitCache;
...
-            if (controllerManager.getDisplayUnitCache() != null) {
-                CleverTapDisplayUnit displayUnit = controllerManager.getDisplayUnitCache()
-                        .getDisplayUnitForID(unitID);
+            DisplayUnitCache cache = controllerManager.getDisplayUnitCache();
+            if (cache != null) {
+                CleverTapDisplayUnit displayUnit = cache.getDisplayUnitForID(unitID);
                 if (displayUnit != null) {

Apply the same pattern in pushDisplayUnitViewedEventForID(...).

Also applies to: 236-244

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clevertap-core/src/main/java/com/clevertap/android/sdk/AnalyticsManager.java`
around lines 204 - 216, The code performs a TOCTOU check on
controllerManager.getDisplayUnitCache(): first checking for null and then
calling getDisplayUnitForID(), which can race if the cache is swapped; to fix,
capture controllerManager.getDisplayUnitCache() into a local variable (e.g.,
displayUnitCache) at the start of pushDisplayUnitViewedEventForID(...) and use
that local reference for both the null check and the subsequent
getDisplayUnitForID() call (and similarly for the other repeated block around
lines 236-244), ensuring the same cache instance is used for the entire logic
path before queuing the event and calling coreMetaData.setWzrkParams().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clevertap-core/src/main/java/com/clevertap/android/sdk/CleverTapAPI.java`:
- Around line 1475-1476: In CleverTapAPI, avoid the TOCTOU by capturing the
display unit cache once into a local variable before the null-check and then
using that variable for getAllDisplayUnits(); specifically, in the blocks that
call coreState.getControllerManager().getDisplayUnitCache() (the occurrences
around the current lines and the similar code near line 1794), assign the result
to a local variable (e.g., displayUnitCache) and use that for the null-check and
subsequent getAllDisplayUnits() call so the same instance is used throughout the
method.

In
`@clevertap-core/src/main/java/com/clevertap/android/sdk/ControllerManager.java`:
- Line 31: The shared field displayUnitCache in ControllerManager is written by
setDisplayUnitCache() and read by getDisplayUnitCache() without synchronization,
causing visibility races; fix it by making the field volatile (or alternatively
synchronizing both setDisplayUnitCache() and getDisplayUnitCache()) so writes
are immediately visible to readers like those in CleverTapAPI and after
DisplayUnitResponse initialization, ensuring consistent cross-thread visibility
of the DisplayUnitCache reference.

In
`@clevertap-core/src/main/java/com/clevertap/android/sdk/response/DisplayUnitResponse.java`:
- Around line 87-94: The code re-reads controllerManager.getDisplayUnitCache()
after leaving the synchronized(displayUnitControllerLock) block, allowing a
concurrent setDisplayUnitCache() to swap the cache and potentially drop or
misapply the parsed payload; fix it by capturing the cache instance inside the
synchronized block (use the same DisplayUnitCache reference created or returned
within the synchronized section) and then call
cache.updateDisplayUnits(messages) on that captured instance (referencing
displayUnitControllerLock, controllerManager.getDisplayUnitCache(),
controllerManager.setDisplayUnitCache(), DisplayUnitCache cache, and
updateDisplayUnits(messages)) so the lazy-install and update operate on the
exact same cache object.

---

Outside diff comments:
In
`@clevertap-core/src/main/java/com/clevertap/android/sdk/AnalyticsManager.java`:
- Around line 204-216: The code performs a TOCTOU check on
controllerManager.getDisplayUnitCache(): first checking for null and then
calling getDisplayUnitForID(), which can race if the cache is swapped; to fix,
capture controllerManager.getDisplayUnitCache() into a local variable (e.g.,
displayUnitCache) at the start of pushDisplayUnitViewedEventForID(...) and use
that local reference for both the null check and the subsequent
getDisplayUnitForID() call (and similarly for the other repeated block around
lines 236-244), ensuring the same cache instance is used for the entire logic
path before queuing the event and calling coreMetaData.setWzrkParams().

In
`@clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginController.java`:
- Around line 293-298: In LoginController, avoid calling
controllerManager.getDisplayUnitCache() twice: capture its result into a local
variable (e.g., displayUnitCache) before the null check, then call
displayUnitCache.reset() if non-null; otherwise call config.getLogger().verbose
with the Constants.FEATURE_DISPLAY_UNIT message. This prevents a race where the
cache can be swapped between the null-check and reset, which could skip reset or
throw a NPE.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ea70abc3-382a-4df7-a5bf-b527d041e093

📥 Commits

Reviewing files that changed from the base of the PR and between f3ca626 and 1325170.

📒 Files selected for processing (8)
  • clevertap-core/src/main/java/com/clevertap/android/sdk/AnalyticsManager.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/CleverTapAPI.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/ControllerManager.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitController.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/displayunits/DisplayUnitCache.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/login/LoginController.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/response/DisplayUnitResponse.java
  • clevertap-core/src/test/java/com/clevertap/android/sdk/AnalyticsManagerTest.kt

Comment thread clevertap-core/src/main/java/com/clevertap/android/sdk/CleverTapAPI.java Outdated
Comment thread clevertap-core/src/main/java/com/clevertap/android/sdk/ControllerManager.java Outdated
Comment on lines +87 to +94
synchronized (displayUnitControllerLock) {// lock to avoid multiple instance creation for controller
if (controllerManager.getCTDisplayUnitController() == null) {
controllerManager.setCTDisplayUnitController(new CTDisplayUnitController());
if (controllerManager.getDisplayUnitCache() == null) {
controllerManager.setDisplayUnitCache(new CTDisplayUnitController());
}
}
ArrayList<CleverTapDisplayUnit> displayUnits = controllerManager.getCTDisplayUnitController()
.updateDisplayUnits(messages);
DisplayUnitCache cache = controllerManager.getDisplayUnitCache();
ArrayList<CleverTapDisplayUnit> displayUnits = cache != null
? cache.updateDisplayUnits(messages) : null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the lazy-install and update on the same cache instance.

Line 92 re-reads the cache after the synchronized install path, so a concurrent setDisplayUnitCache() call can replace the controller between installation and updateDisplayUnits(). That can drop the parsed payload or apply it to the wrong cache instance.

🔧 Proposed fix
-        synchronized (displayUnitControllerLock) {// lock to avoid multiple instance creation for controller
-            if (controllerManager.getDisplayUnitCache() == null) {
-                controllerManager.setDisplayUnitCache(new CTDisplayUnitController());
-            }
-        }
-        DisplayUnitCache cache = controllerManager.getDisplayUnitCache();
+        DisplayUnitCache cache;
+        synchronized (displayUnitControllerLock) { // lock to avoid multiple instance creation for controller
+            cache = controllerManager.getDisplayUnitCache();
+            if (cache == null) {
+                cache = new CTDisplayUnitController();
+                controllerManager.setDisplayUnitCache(cache);
+            }
+        }
         ArrayList<CleverTapDisplayUnit> displayUnits = cache != null
                 ? cache.updateDisplayUnits(messages) : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clevertap-core/src/main/java/com/clevertap/android/sdk/response/DisplayUnitResponse.java`
around lines 87 - 94, The code re-reads controllerManager.getDisplayUnitCache()
after leaving the synchronized(displayUnitControllerLock) block, allowing a
concurrent setDisplayUnitCache() to swap the cache and potentially drop or
misapply the parsed payload; fix it by capturing the cache instance inside the
synchronized block (use the same DisplayUnitCache reference created or returned
within the synchronized section) and then call
cache.updateDisplayUnits(messages) on that captured instance (referencing
displayUnitControllerLock, controllerManager.getDisplayUnitCache(),
controllerManager.setDisplayUnitCache(), DisplayUnitCache cache, and
updateDisplayUnits(messages)) so the lazy-install and update operate on the
exact same cache object.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

Code Coverage Debug

Overall Project 68.38% -0.17%
Files changed 50.6%

Module Coverage
clevertap-core 67.3% -0.19%
Files
Module File Coverage
clevertap-core CTDisplayUnitController.java 100%
BaseAnalyticsManager.java 100%
AnalyticsManager.java 87.8% -1.01%
LoginController.java 59.81%
CleverTapAPI.java 31.22% -1.24%
ControllerManager.java 24.59% -8.2%
DisplayUnitResponse.java 10.48% -49.05%

@CTLalit CTLalit force-pushed the task/SDK-5763-display-unit-cache-interface branch from 1325170 to 6a50498 Compare May 5, 2026 12:04
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
clevertap-core/src/test/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitControllerTest.kt (1)

63-79: ⚡ Quick win

Add one test for blank/invalid unit IDs being ignored.

The suite covers null/empty-list and reset paths well, but it doesn’t assert the new filtering behavior for invalid IDs. Please add a case with a blank-id unit and verify it is not retrievable from cache.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@clevertap-core/src/test/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitControllerTest.kt`
around lines 63 - 79, Add a new unit test in CTDisplayUnitControllerTest that
calls ctDisplayUnitController.updateDisplayUnits with a list containing a
CleverTapDisplayUnit (or mock from mockDisplayUnits) whose id is blank/empty,
then assert that ctDisplayUnitController.allDisplayUnits does not include that
unit and that ctDisplayUnitController.getDisplayUnitForID("") (or the blank id
used) returns null; place this alongside existing tests (e.g.,
test_updateDisplayUnits_validList_populatesCache and
test_updateDisplayUnits_replacesPreviousCache) to verify blank/invalid IDs are
ignored by updateDisplayUnits and not stored in the cache.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@clevertap-core/src/test/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitControllerTest.kt`:
- Around line 63-79: Add a new unit test in CTDisplayUnitControllerTest that
calls ctDisplayUnitController.updateDisplayUnits with a list containing a
CleverTapDisplayUnit (or mock from mockDisplayUnits) whose id is blank/empty,
then assert that ctDisplayUnitController.allDisplayUnits does not include that
unit and that ctDisplayUnitController.getDisplayUnitForID("") (or the blank id
used) returns null; place this alongside existing tests (e.g.,
test_updateDisplayUnits_validList_populatesCache and
test_updateDisplayUnits_replacesPreviousCache) to verify blank/invalid IDs are
ignored by updateDisplayUnits and not stored in the cache.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ce0b5982-f9ff-4a51-bf55-0f8285fb1fba

📥 Commits

Reviewing files that changed from the base of the PR and between 90ca488 and 71346b7.

📒 Files selected for processing (4)
  • clevertap-core/src/main/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitController.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/displayunits/DisplayUnitCache.java
  • clevertap-core/src/main/java/com/clevertap/android/sdk/response/DisplayUnitResponse.java
  • clevertap-core/src/test/java/com/clevertap/android/sdk/displayunits/CTDisplayUnitControllerTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • clevertap-core/src/main/java/com/clevertap/android/sdk/displayunits/DisplayUnitCache.java

@CTLalit CTLalit force-pushed the task/SDK-5763-display-unit-cache-interface branch from f26bea0 to 69b43ff Compare May 19, 2026 07:28
CTLalit and others added 6 commits May 19, 2026 22:52
Extracts the public surface of CTDisplayUnitController into a new
DisplayUnitCache interface and exposes CleverTapAPI.setDisplayUnitCache()
so external SDKs (e.g. Native Display) can install their own cache
implementation. Default behaviour preserved when the setter is not
called — the SDK lazily installs CTDisplayUnitController on first
adUnit_notifs response.

- displayunits/DisplayUnitCache.java: new public interface
- displayunits/CTDisplayUnitController.java: implements DisplayUnitCache
- ControllerManager.java: holds DisplayUnitCache reference; new
  getDisplayUnitCache()/setDisplayUnitCache(); deprecated old getters
- CleverTapAPI.java: setDisplayUnitCache() public; lookup methods route
  via interface
- AnalyticsManager, DisplayUnitResponse, LoginController: route via
  getDisplayUnitCache()
- AnalyticsManagerTest: mock points updated to displayUnitCache
…ID, additionalProperties)

Adds a dedicated Core SDK method for tracking element-level clicks
within a Native Display unit, separate from the existing unit-level
pushDisplayUnitClickedEventForID. The new method:

- Carries the campaign's wzrk_* fields from the cached unit JSON (same
  enrichment as the unit-level method).
- Adds wzrk_element_id = elementID to evtData from the dedicated
  parameter.
- Merges additionalProperties into evtData after wzrk_* enrichment.
  Keys starting with wzrk_ are stripped defensively — the prefix is
  reserved for server-controlled attribution.

The existing pushDisplayUnitClickedEventForID(unitId) stays untouched
for unit-level clicks (graceful degradation for older ND SDK clients).

Why a new method (vs an overload of the existing one):
- Method name encodes the semantic — readers/dashboard query authors
  don't need tribal knowledge to interpret "click + additionalProperties".
- ND SDK PR #32 wires its reflective probe to this new signature; old
  Core SDK callers continue to call the legacy single-arg method
  unchanged. No silent attribution semantics drift.

Tests in AnalyticsManagerTest cover:
- Happy path: elementID lands as wzrk_element_id; additionalProperties
  merge as first-class evtData fields.
- wzrk_* keys in additionalProperties are filtered out.
- coreMetaData.setWzrkParams (which feeds wzrk_ref batch headers)
  receives only wzrk_* keys — caller-supplied non-wzrk extras don't
  ride along on subsequent batched events.
- displayUnitCache null short-circuit preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…o response

Refactor the DisplayUnitCache interface so updateDisplayUnits takes
List<CleverTapDisplayUnit> instead of JSONArray and is void. The cache
contract is now pure-model — JSON parsing lives in DisplayUnitResponse,
not in cache implementations.

- DisplayUnitCache: parameter is List<CleverTapDisplayUnit>; return removed
  (caller already has the list).
- CTDisplayUnitController: resets and populates the items map from the
  supplied list, skipping null/empty-id entries.
- DisplayUnitResponse: new parseDisplayUnitsFromJson(JSONArray) filters
  malformed entries (error field) and per-item JSONException, then hands
  the model list to the cache and notifies the listener.
- CTDisplayUnitControllerTest: rewritten against the list-based API.

Host-installed DisplayUnitCache implementations no longer need to know
about the SDK's JSON shape; they only deal with model objects.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…ay-unit cache

- ControllerManager.displayUnitCache is now volatile so writes from
  setDisplayUnitCache() (any thread, including a public-API caller outside
  the synchronized init block in DisplayUnitResponse) are safely visible to
  subsequent reads.
- CleverTapAPI.getAllDisplayUnits() and getDisplayUnitForId() now capture
  the cache reference into a local variable once, then null-check and
  dereference that local. Closes the TOCTOU window where a concurrent
  setDisplayUnitCache(null) could let the second getDisplayUnitCache()
  return null after the guard had passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…leverTapAPI

Adds the DisplayUnitCache import in CleverTapAPI.java, switches the
setDisplayUnitCache(...) parameter to the short name, and replaces the
fully-qualified DisplayUnitCache references inside getAllDisplayUnits()
and getDisplayUnitForId() (introduced when the import wasn't yet present)
with the short name as well.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@piyush-kukadiya piyush-kukadiya force-pushed the task/SDK-5763-display-unit-cache-interface branch from 935ef49 to 3cda547 Compare May 19, 2026 17:22
@piyush-kukadiya piyush-kukadiya merged commit e3090b2 into develop May 19, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants