Skip to content

SDK-6037: Flutter slot placement system (SlotManager + NativeDisplaySlot) - #84

Open
CTLalit wants to merge 8 commits into
feat/SDK-5821-flutter-modelsfrom
feat/SDK-6037-flutter-slot-system
Open

SDK-6037: Flutter slot placement system (SlotManager + NativeDisplaySlot)#84
CTLalit wants to merge 8 commits into
feat/SDK-5821-flutter-modelsfrom
feat/SDK-6037-flutter-slot-system

Conversation

@CTLalit

@CTLalit CTLalit commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the server-driven slot placement system to the Flutter plugin, at parity with Android (NativeDisplaySlotManager + NativeDisplaySlot) and iOS. Previously the plugin had no placement layer at allslot_id was parsed into NativeDisplayUnit, but nothing routed units to slots.

A "slot" is a named, server-driven placement zone. Units carry a top-level slot_id; a view registers as an observer for a slot id and the manager delivers the latest matching unit to it. This is a placement/bridge concept — not a layout/z-order concept.

Jira: SDK-6037 (epic SDK-5820)

What's included

SDK (flutter/lib)

  • NativeDisplaySlotManager singleton — registerSlot / unregisterSlot (immediate delivery of a cached unit on late registration), getActiveSlotIds, getUnit, clearSlot / clearAll, routing by slot_id.
  • IngestionprocessDisplayUnits(List) / processDisplayUnit(...) parse raw CleverTap display-unit JSON off-main via the existing NativeDisplayConfigParser, then route. deliverUnits(...) for hosts that parse themselves.
  • NativeDisplaySlot widget — registers for a slot, renders the delivered unit via NativeDisplayView (reusing pre-resolved styles), shows a loadingBuilder otherwise, forwards onNotificationViewed / onNotificationClicked attribution to the host, re-registers on slotId change, unregisters on dispose.
  • Server slot-syncsyncActiveSlots() emits wzrk_nd_slot_sync with {"slots": [...]} via an onSlotSync callback.
  • New public API exported from the plugin barrel.

Sample (flutter-sample)

  • Rewrote the Slot Demo screen from a static dashed-placeholder mockup into a real NativeDisplaySlot-driven feed. "Fetch Slot Data" injects sample display-unit JSON (standing in for clevertap_plugin display units) into processDisplayUnits; the original dashed "Ad" box becomes the slot loadingBuilder. Attribution + slot-sync forwarded via clevertap_plugin, matching clevertap_integration_screen.dart.

Tests

  • 22 Dart unit/widget tests: routing, immediate delivery, active-slot ids, clear, sync payload, parse-and-route ingestion, and slot widget lifecycle.

Key design call — pure-Dart, host-mediated

Native's SlotManager auto-receives units via the bridge listener and calls the Core SDK directly (syncCurrentSlotIds(cleverTapApi)). Flutter's plugin is intentionally pure-Dart (SDK-6019), so it cannot call the Core SDK. Both ends are therefore host-mediated:

  • In — the host forwards CleverTap display-unit JSON (from clevertap_plugin) into processDisplayUnits.
  • Outwzrk_nd_slot_sync is emitted via the onSlotSync callback, which the host wires to CleverTapPlugin.recordEvent.

This mirrors how attribution was already surfaced to the client in SDK-6019/6020.

Verification

  • flutter analyze — clean (SDK + sample).
  • flutter test — full suite green (incl. 22 new placement tests).

Base branch

Targets feat/SDK-5821-flutter-models (the Flutter integration branch this stack lands on), not main.

🤖 Generated with Claude Code

…lot)

Bring the server-driven slot placement system to the Flutter plugin, at
parity with Android and iOS. Previously `slot_id` was parsed into
`NativeDisplayUnit` but nothing routed units to slots.

SDK (flutter/lib):
- NativeDisplaySlotManager singleton: registerSlot/unregisterSlot with
  immediate delivery of a cached unit on late registration, getActiveSlotIds,
  clearSlot/clearAll, and routing by top-level slot_id.
- Ingestion: processDisplayUnits/processDisplayUnit parse raw CleverTap
  display-unit JSON off-main via NativeDisplayConfigParser, then route.
- NativeDisplaySlot widget: registers for a slot, renders the delivered unit
  via NativeDisplayView (reusing pre-resolved styles), shows a loadingBuilder
  otherwise, and forwards viewed/clicked attribution to the host.
- Server slot-sync: wzrk_nd_slot_sync event with {"slots":[...]} via an
  onSlotSync callback — the pure-Dart plugin cannot call the Core SDK
  directly, so the host forwards it through clevertap_plugin.
- Export the new public API from the plugin barrel.

Sample (flutter-sample): rewrote the Slot Demo screen from a static mockup
into a real NativeDisplaySlot-driven feed fed by locally-injected sample
display-unit JSON; wires attribution + slot-sync via clevertap_plugin.

Tests: 22 Dart unit/widget tests covering routing, immediate delivery,
active-slot ids, clear, sync payload, parse-and-route ingestion, and the
slot widget lifecycle. Full flutter suite green; both packages analyze clean.

Divergence from native is intentional: native's SlotManager auto-receives
units via the bridge listener and calls the Core SDK directly. Flutter's
plugin is pure-Dart (SDK-6019), so ingestion and slot-sync are host-mediated.

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • develop

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ac6fbb5-d664-4aca-ad8e-cafab2d51498

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

CTLalit and others added 7 commits August 13, 2026 14:05
The Slot Demo "Fetch Slot Data" button parsed and routed nothing — no
banners rendered — because the sample built layout `padding` as nested
{value,unit} objects with start/end. Spacing.fromJson expects flat per-side
numbers (top/bottom/left/right) with a single `unit`, so the cast threw,
NativeDisplayConfigParser._parseSync swallowed it, and every unit was dropped.

- flutter-sample: correct the three padding blocks in the sample banner JSON
  to the flat-number shape the model expects.
- flutter-sample: set the sample log level to NDLogLevel.verbose for debugging.
- parser: log a warning when a unit fails to parse instead of dropping it
  silently — warnings emit at INFO+ so this shows even inside the parse
  isolate. A malformed payload is now visible, not invisible.
- tests: parser regression suite covering the banner container/padding
  contract, the nested-padding drop (no throw), and non-map input.

All flutter tests green; both packages analyze clean.

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pre-existing infra blocked `flutter run` on both platforms (unrelated to the
slot feature, which is pure Dart):

- iOS Podfile: pointed CleverTapNativeDisplay at ../../../ios, but the podspec
  moved to the repo root in #75 — corrected to ../.. . Also set
  `platform :ios, '15.0'` (the podspec's floor) so pod install stops defaulting
  to 12.0 and failing resolution.
- Android: clevertap_plugin 4.1.0 declares core SDK 8.3.0 but its bundled
  CleverTapListenerProxy needs 8.4.x listener interfaces, so the plugin module
  fails to compile ("Unresolved reference: CleverTapListenerProxy"). Force
  clevertap-android-sdk:8.4.1 across all subprojects (an app-level force does
  not reach the plugin's own compile classpath).

Also bump the Slot Demo banner height 96dp -> 120dp so the title + subtitle +
CTA fit within the 16dp padding (96dp overflowed the column by ~12dp).

Verified: app builds, installs and runs on Android emulator and iOS simulator;
tapping "Fetch Slot Data" renders the injected banners in their slots.

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m parity

Port the Android/iOS event-screenshot automation suites to the Flutter sample
so all three platforms fire the same events against the same live account and
capture comparable screenshots.

Slot IDs / feed already matched Android + iOS (slot_top, slot_feed_1,
slot_feed_2, slot_bottom at feed indices 0/4/8/18 among 15 app items). The real
alignment: the Slot Demo "Fetch Slot Data" button now fires the SAME real events
as the native samples (Footer1, Footer5, Header1, Header2, Header4, lalit) and
populates slots from server-returned campaigns — replacing the local sample-JSON
injection so the comparison is apples-to-apples.

Sample changes:
- clevertap_integration_screen: add automation hooks (ct-event-input,
  ct-send-event-btn) and an event-log hide toggle (event-log-toggle /
  event-log-content), matching the native testTags/accessibility ids. The
  single display-units handler now also fans parsed units to the SlotManager
  (clevertap_plugin exposes one handler, unlike the native multi-listener
  bridge), so slotted units reach the Slots tab.
- slot_demo_screen: fire the canonical fetch events; keys on the fetch button
  and feed for the automation.
- main: discard CleverTap in-apps under --dart-define=ND_AUTOMATION=true so
  native in-app overlays don't cover the Flutter UI during a screenshot run.

Automation suite (integration_test + flutter drive):
- automation_helpers.dart: eventsToFire (byte-identical to Android EVENTS_TO_FIRE
  / iOS EventsToFire), waits, tab/log/fire/screenshot helpers.
- events_screenshots_test.dart: fire all 22 events, screenshot each.
- slots_screenshots_test.dart: fetch + scroll, screenshot initial/after_fetch/
  at_bottom/back_at_top.
- test_driver/integration_test.dart: writes PNGs to
  ~/Desktop/nd-automation-output/flutter/ (next to the Android/iOS captures).
- run_automation.sh: drives both targets on a device/emulator.

Verified on the Android emulator: both suites pass; 26 screenshots captured,
showing real server campaigns in the canvas and routed into slot_top.

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fore shot)

The per-event settle waited a fixed 1.5s then pumped a single frame, so a
campaign that arrived late in the window — or an image that decoded just after
that pump — could be missed or captured stale. Pump repeatedly across the whole
wall-clock window (150ms steps) so both the async server response and async
image decode paint into the surface before the screenshot, plus a trailing
frame. Raise per-event wait to 3s and fetch-slot wait to 4s for image headroom.

Note: ND static images use CachedNetworkImage (disk cache), so they persist
across runs; GIFs/tiles use Image.network (memory only).

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ample

- AutomationTestHelpers.captureScreenshot: replace the deprecated
  androidx.test.runner.screenshot.Screenshot / BasicScreenCaptureProcessor with
  UiAutomation.takeScreenshot() + Bitmap.compress written into AGP's
  additionalTestOutputDir. Same output location (the automationScreenshots task
  still pulls it), no deprecated API. All callers (Events/Slots tests) go
  through this helper.
- SampleApplication: suspend in-app notifications app-wide right after CleverTap
  init. This is a Native Display demo — an in-app overlay would cover the canvas
  / slots and break those flows (and the automation suite's captures). Suspended
  in-apps are held, not shown; resumeInAppNotifications() re-enables them.

Both compile (:app:compileDebugKotlin + :app:compileDebugAndroidTestKotlin).

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…omation

The Events automation typed + fired + captured with only a 1.5s wait, so on a
live account the screenshot was taken before the campaign round-tripped and its
images loaded — captures showed empty/half-rendered canvases. Add a shared
SCREENSHOT_SETTLE_DELAY_MS = 5s and use it after each event fire (Compose + XML
loops) so the server response, native-display render, and Coil image load all
complete before capture.

Note: the instrumented automationScreenshots suite has no image prewarm — that
exists only in the Robolectric unit screenshot tests (ImagePrewarmCache /
buildPrewarmedImageLoader via LocalImageLoader in NativeDisplayScreenshotTest).
The instrumented path loads images live, so it relies on this wait (+ Coil's
disk cache on re-runs).

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r change)

Discussed cleanups only — models/containers/elements structure untouched.

1. DimensionCalculator: a util with a single caller (HtmlElement) while every
   container did percent math inline. Inlined its resolution into HtmlElement
   (percent → rootHeight%, dp/sp/px → value, specials → null) and deleted the
   class + its now-orphaned unit-test group (sizing is covered by the widget
   tests).

2. Four per-view InheritedWidgets (RootHeightScope / ResolvedStylesScope /
   UnitIdScope / NotificationClickScope) collapsed into one NativeDisplayScope.
   These values are set once per NativeDisplayView render and never change
   independently, so a single scope is strictly equivalent (no rebuild-
   granularity loss) and drops 4 files → 1. It stays per-NativeDisplayView, so
   it's unaffected by where the client places NativeDisplaySlot (Row/Column/
   List/full-screen). The NativeDisplayNotificationClicked typedef moved here
   and is still re-exported via native_display_view.dart.

All readers updated (renderer, text/image/button/video/html elements, gallery).
flutter analyze clean; full suite green (232 tests).

Jira: https://wizrocket.atlassian.net/browse/SDK-6037

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant