SDK-6037: Flutter slot placement system (SlotManager + NativeDisplaySlot) - #84
Open
CTLalit wants to merge 8 commits into
Open
SDK-6037: Flutter slot placement system (SlotManager + NativeDisplaySlot)#84CTLalit wants to merge 8 commits into
CTLalit wants to merge 8 commits into
Conversation
…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>
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 all —slot_idwas parsed intoNativeDisplayUnit, 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)NativeDisplaySlotManagersingleton —registerSlot/unregisterSlot(immediate delivery of a cached unit on late registration),getActiveSlotIds,getUnit,clearSlot/clearAll, routing byslot_id.processDisplayUnits(List)/processDisplayUnit(...)parse raw CleverTap display-unit JSON off-main via the existingNativeDisplayConfigParser, then route.deliverUnits(...)for hosts that parse themselves.NativeDisplaySlotwidget — registers for a slot, renders the delivered unit viaNativeDisplayView(reusing pre-resolved styles), shows aloadingBuilderotherwise, forwardsonNotificationViewed/onNotificationClickedattribution to the host, re-registers onslotIdchange, unregisters on dispose.syncActiveSlots()emitswzrk_nd_slot_syncwith{"slots": [...]}via anonSlotSynccallback.Sample (
flutter-sample)NativeDisplaySlot-driven feed. "Fetch Slot Data" injects sample display-unit JSON (standing in forclevertap_plugindisplay units) intoprocessDisplayUnits; the original dashed "Ad" box becomes the slotloadingBuilder. Attribution + slot-sync forwarded viaclevertap_plugin, matchingclevertap_integration_screen.dart.Tests
Key design call — pure-Dart, host-mediated
Native's
SlotManagerauto-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:clevertap_plugin) intoprocessDisplayUnits.wzrk_nd_slot_syncis emitted via theonSlotSynccallback, which the host wires toCleverTapPlugin.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), notmain.🤖 Generated with Claude Code