Skip to content

feat(SDK-5820): Flutter Native Display SDK — complete implementation - #38

Open
CTLalit wants to merge 30 commits into
mainfrom
feat/SDK-5821-flutter-models
Open

feat(SDK-5820): Flutter Native Display SDK — complete implementation#38
CTLalit wants to merge 30 commits into
mainfrom
feat/SDK-5821-flutter-models

Conversation

@CTLalit

@CTLalit CTLalit commented May 25, 2026

Copy link
Copy Markdown
Contributor

Jira

  • SDK-5820 — Flutter Native Display SDK (parent epic)

Summary

Complete Flutter implementation of the Native Display SDK — pure Dart renderer with platform channel bridge for CleverTap Core SDK integration. Mirrors the production Android (Kotlin/Compose) and iOS (Swift/SwiftUI) implementations.

This PR consolidates 15 stacked PRs. Each individual PR is listed below with its scope for reviewers who want to follow the implementation step by step.


Consolidated PRs

PR Ticket Scope
#39 SDK-5822 Business logic — StyleResolver, VariableEvaluator, ColorParser, DimensionCalculator
#40 SDK-5823 NativeDisplayView entry point + renderer infrastructure (RootHeightScope, ResolvedStylesScope)
#41 SDK-5824 BOX container (Stack + Positioned) + VERTICAL/HORIZONTAL/GALLERY stubs
#42 SDK-5825 StyleApplier — backgrounds, borders, shadows, opacity
#43 SDK-5826 TEXT element renderer
#44 SDK-5827 IMAGE element renderer
#45 SDK-5828 BUTTON, SPACER, DIVIDER element renderers
#46 SDK-5829 GALLERY container — SNAPPING mode with PageController + auto-scroll
#47 SDK-5830 VIDEO element renderer with VideoPlayerController
#48 SDK-5831 HTML element renderer with WebViewController
#49 SDK-5832 Entrance animations via AnimationModifier
#50 SDK-5833 ActionHandler — action routing with url_launcher
#51 SDK-5834 Platform channel bridge — NativeDisplayBridge + Android/iOS plugin stubs
#52 SDK-5835 Flutter sample app + aspectRatio sizing fix + VERTICAL/HORIZONTAL container promotion
SDK-5837 Move platform channel parsing into SDK (NativeDisplayConfigParser + NativeDisplayUnit)
SDK-5838 Production-grade renderer performance (StatefulWidget, RepaintBoundary, CachedNetworkImage, Opacity)

Architecture

Pure Dart renderer — no AndroidView/UiKitView. Platform channels are used only for the CleverTap Core SDK bridge.

flutter/
├── lib/src/models/          # All data models (manual fromJson, no code-gen)
├── lib/src/style/           # StyleResolver — cascading style resolution
├── lib/src/evaluator/       # VariableEvaluator — {{template}} interpolation
├── lib/src/utils/           # ColorParser (RGBA→ARGB), DimensionCalculator
├── lib/src/renderer/        # NativeDisplayView + NativeDisplayRenderer
│   ├── containers/          # BOX, VERTICAL, HORIZONTAL, GALLERY
│   └── elements/            # TEXT, IMAGE, BUTTON, VIDEO, HTML, SPACER, DIVIDER
├── lib/src/bridge/          # NativeDisplayBridge, NativeDisplayConfigParser
└── lib/src/handler/         # ActionHandler

Key design decisions

  • Manual fromJson — no json_serializable/freezed; Dart 3.0 switch expressions
  • Style pre-resolutionStyleResolver.resolveAll() runs once in StatefulWidget.initState(), not per rebuild
  • Off-main parsingNativeDisplayConfigParser uses Isolate.run() for deep-cast + 3-strategy extraction + style pre-resolution; falls back to synchronous on Dart < 3.4
  • aspectRatio precedence — when aspectRatio > 0, percent width is ignored and full parent width is used (matches Android/iOS)
  • RepaintBoundary on images, video, gallery items to isolate expensive repaints
  • Color.withValues(alpha:) instead of Opacity widget for solid backgrounds (avoids saveLayer)

Test plan

  • flutter analyze — no issues in flutter/ and flutter-sample/
  • flutter test — all tests pass
  • flutter build apk --debug --target-platform android-arm64 — builds clean
  • Reviewer: confirm Style.merge() priority (inline > styleClass > theme)
  • Reviewer: confirm aspectRatio overrides percent width on all layout paths
  • Reviewer: confirm NativeDisplayConfigParser 3-strategy extraction matches Android NativeDisplayConfigParser.kt

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 391 files, which is 291 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: adcb7171-9b84-4498-aa0e-0ab9de262d8d

📥 Commits

Reviewing files that changed from the base of the PR and between d90b564 and ecd8437.

⛔ Files ignored due to path filters (38)
  • flutter-sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • flutter-sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • flutter-sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • flutter-sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • flutter-sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png is excluded by !**/*.png
  • flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png is excluded by !**/*.png
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png is excluded by !**/*.png
  • flutter-sample/pubspec.lock is excluded by !**/*.lock
  • flutter-sample/web/favicon.png is excluded by !**/*.png
  • flutter-sample/web/icons/Icon-192.png is excluded by !**/*.png
  • flutter-sample/web/icons/Icon-512.png is excluded by !**/*.png
  • flutter-sample/web/icons/Icon-maskable-192.png is excluded by !**/*.png
  • flutter-sample/web/icons/Icon-maskable-512.png is excluded by !**/*.png
  • flutter-sample/windows/runner/resources/app_icon.ico is excluded by !**/*.ico
  • flutter/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (391)
  • .claude/agents/android-sdk.md
  • .claude/agents/android-sdk/knowledge/gotchas.md
  • .claude/agents/android-sdk/knowledge/rendering-pipeline.md
  • .claude/agents/flutter-sample.md
  • .claude/agents/flutter-sample/knowledge/sample-architecture.md
  • .claude/agents/flutter-sdk.md
  • .claude/agents/flutter-sdk/examples/native_display_view.dart
  • .claude/agents/flutter-sdk/knowledge/architecture.md
  • .claude/agents/flutter-sdk/knowledge/performance.md
  • .claude/agents/flutter-sdk/knowledge/platform-channels.md
  • .claude/agents/flutter-sdk/knowledge/rendering-pipeline.md
  • .claude/agents/ios-sdk.md
  • .claude/agents/ios-sdk/knowledge/architecture.md
  • .claude/agents/testing/knowledge/json-generation-rules.md
  • .claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md
  • .claude/reference/JSON_STRUCTURE_REFERENCE.md
  • .gitignore
  • CLAUDE.md
  • docs/JSON_STRUCTURE_REFERENCE.md
  • flutter-sample/.flutter-plugins
  • flutter-sample/.flutter-plugins-dependencies
  • flutter-sample/.gitignore
  • flutter-sample/.metadata
  • flutter-sample/README.md
  • flutter-sample/analysis_options.yaml
  • flutter-sample/android/.gitignore
  • flutter-sample/android/app/build.gradle.kts
  • flutter-sample/android/app/src/debug/AndroidManifest.xml
  • flutter-sample/android/app/src/main/AndroidManifest.xml
  • flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/MainActivity.kt
  • flutter-sample/android/app/src/main/kotlin/com/clevertap/flutter/clevertap_native_display_sample/SampleApplication.kt
  • flutter-sample/android/app/src/main/res/drawable-v21/launch_background.xml
  • flutter-sample/android/app/src/main/res/drawable/launch_background.xml
  • flutter-sample/android/app/src/main/res/values-night/styles.xml
  • flutter-sample/android/app/src/main/res/values/styles.xml
  • flutter-sample/android/app/src/profile/AndroidManifest.xml
  • flutter-sample/android/build.gradle.kts
  • flutter-sample/android/gradle.properties
  • flutter-sample/android/gradle/wrapper/gradle-wrapper.properties
  • flutter-sample/android/settings.gradle.kts
  • flutter-sample/assets/banners/banner-01-hero-summer-sale.json
  • flutter-sample/assets/banners/banner-02-product-iphone.json
  • flutter-sample/assets/banners/banner-03-announcement-update.json
  • flutter-sample/assets/banners/banner-04-travel-deals.json
  • flutter-sample/assets/banners/banner-05-fashion-collection.json
  • flutter-sample/assets/banners/banner-06-credit-card-offer.json
  • flutter-sample/assets/banners/banner-07-app-rating.json
  • flutter-sample/assets/banners/banner-08-flash-sale.json
  • flutter-sample/assets/banners/banner-09-premium-subscription.json
  • flutter-sample/assets/banners/banner-10-welcome-onboarding.json
  • flutter-sample/assets/configs/animation_container_and_children.json
  • flutter-sample/assets/configs/animation_container_fade.json
  • flutter-sample/assets/configs/animation_staggered_children.json
  • flutter-sample/assets/configs/arrangement_demo.json
  • flutter-sample/assets/configs/bridge_mock_notification.json
  • flutter-sample/assets/configs/bridge_mock_product.json
  • flutter-sample/assets/configs/gallery_three_modes.json
  • flutter-sample/assets/configs/home_screen.json
  • flutter-sample/assets/configs/showcase_dashboard.json
  • flutter-sample/assets/configs/showcase_ecommerce_product.json
  • flutter-sample/assets/configs/showcase_profile_screen.json
  • flutter-sample/assets/configs/showcase_social_profile.json
  • flutter-sample/assets/configs/test_simple.json
  • flutter-sample/assets/test-configs/test-001-vertical-simple.json
  • flutter-sample/assets/test-configs/test-002-horizontal-simple.json
  • flutter-sample/assets/test-configs/test-003-box-simple.json
  • flutter-sample/assets/test-configs/test-004-stack-simple.json
  • flutter-sample/assets/test-configs/test-005-gallery-simple.json
  • flutter-sample/assets/test-configs/test-006-vertical-empty.json
  • flutter-sample/assets/test-configs/test-007-vertical-single-child.json
  • flutter-sample/assets/test-configs/test-008-vertical-3-children.json
  • flutter-sample/assets/test-configs/test-009-vertical-5-children.json
  • flutter-sample/assets/test-configs/test-010-vertical-10-children.json
  • flutter-sample/assets/test-configs/test-011-horizontal-empty.json
  • flutter-sample/assets/test-configs/test-012-horizontal-single-child.json
  • flutter-sample/assets/test-configs/test-013-horizontal-3-children.json
  • flutter-sample/assets/test-configs/test-014-horizontal-5-children.json
  • flutter-sample/assets/test-configs/test-015-horizontal-10-children.json
  • flutter-sample/assets/test-configs/test-016-box-empty.json
  • flutter-sample/assets/test-configs/test-017-box-single-child.json
  • flutter-sample/assets/test-configs/test-018-box-3-children.json
  • flutter-sample/assets/test-configs/test-019-box-5-children.json
  • flutter-sample/assets/test-configs/test-020-stack-empty.json
  • flutter-sample/assets/test-configs/test-021-stack-single-child.json
  • flutter-sample/assets/test-configs/test-022-stack-3-children.json
  • flutter-sample/assets/test-configs/test-023-stack-5-children.json
  • flutter-sample/assets/test-configs/test-024-gallery-empty.json
  • flutter-sample/assets/test-configs/test-025-gallery-single-child.json
  • flutter-sample/assets/test-configs/test-026-gallery-3-children-snapping.json
  • flutter-sample/assets/test-configs/test-027-gallery-5-children-snapping.json
  • flutter-sample/assets/test-configs/test-028-gallery-10-children-snapping.json
  • flutter-sample/assets/test-configs/test-029-gallery-3-children-free-flow.json
  • flutter-sample/assets/test-configs/test-030-gallery-3-children-free-flow-grid.json
  • flutter-sample/assets/test-configs/test-031-vertical-spaced.json
  • flutter-sample/assets/test-configs/test-032-vertical-space-between.json
  • flutter-sample/assets/test-configs/test-033-vertical-space-evenly.json
  • flutter-sample/assets/test-configs/test-034-vertical-space-around.json
  • flutter-sample/assets/test-configs/test-035-horizontal-start.json
  • flutter-sample/assets/test-configs/test-036-horizontal-center.json
  • flutter-sample/assets/test-configs/test-037-horizontal-end.json
  • flutter-sample/assets/test-configs/test-038-vertical-spacing-0.json
  • flutter-sample/assets/test-configs/test-039-vertical-spacing-8.json
  • flutter-sample/assets/test-configs/test-040-vertical-spacing-16.json
  • flutter-sample/assets/test-configs/test-041-vertical-spacing-32.json
  • flutter-sample/assets/test-configs/test-042-vertical-padding-uniform.json
  • flutter-sample/assets/test-configs/test-043-vertical-padding-individual.json
  • flutter-sample/assets/test-configs/test-044-horizontal-padding-asymmetric.json
  • flutter-sample/assets/test-configs/test-045-box-padding-large.json
  • flutter-sample/assets/test-configs/test-046-vertical-wrap-content.json
  • flutter-sample/assets/test-configs/test-047-horizontal-percent-width.json
  • flutter-sample/assets/test-configs/test-048-vertical-mixed-units.json
  • flutter-sample/assets/test-configs/test-049-nested-mixed-arrangements.json
  • flutter-sample/assets/test-configs/test-050-gallery-spacing-variations.json
  • flutter-sample/assets/test-configs/test-051-all-text-elements.json
  • flutter-sample/assets/test-configs/test-052-all-image-elements.json
  • flutter-sample/assets/test-configs/test-053-all-button-elements.json
  • flutter-sample/assets/test-configs/test-054-all-video-elements.json
  • flutter-sample/assets/test-configs/test-055-all-spacer-elements.json
  • flutter-sample/assets/test-configs/test-056-all-divider-elements.json
  • flutter-sample/assets/test-configs/test-057-product-card.json
  • flutter-sample/assets/test-configs/test-058-login-form.json
  • flutter-sample/assets/test-configs/test-059-profile-header.json
  • flutter-sample/assets/test-configs/test-060-media-player.json
  • flutter-sample/assets/test-configs/test-061-article-layout.json
  • flutter-sample/assets/test-configs/test-062-action-sheet.json
  • flutter-sample/assets/test-configs/test-063-stats-card.json
  • flutter-sample/assets/test-configs/test-064-gallery-item.json
  • flutter-sample/assets/test-configs/test-065-notification.json
  • flutter-sample/assets/test-configs/test-066-pricing-card.json
  • flutter-sample/assets/test-configs/test-067-hero-banner.json
  • flutter-sample/assets/test-configs/test-068-social-post.json
  • flutter-sample/assets/test-configs/test-069-settings-row.json
  • flutter-sample/assets/test-configs/test-070-feature-showcase.json
  • flutter-sample/assets/test-configs/test-071-text-colors.json
  • flutter-sample/assets/test-configs/test-072-font-sizes.json
  • flutter-sample/assets/test-configs/test-073-font-weights.json
  • flutter-sample/assets/test-configs/test-074-text-alignment.json
  • flutter-sample/assets/test-configs/test-075-text-decoration.json
  • flutter-sample/assets/test-configs/test-076-line-height.json
  • flutter-sample/assets/test-configs/test-077-font-families.json
  • flutter-sample/assets/test-configs/test-078-border-radius.json
  • flutter-sample/assets/test-configs/test-079-border-width-color.json
  • flutter-sample/assets/test-configs/test-080-shadows-light.json
  • flutter-sample/assets/test-configs/test-081-shadows-medium.json
  • flutter-sample/assets/test-configs/test-082-shadows-heavy.json
  • flutter-sample/assets/test-configs/test-083-opacity-variations.json
  • flutter-sample/assets/test-configs/test-084-combined-visual-styles.json
  • flutter-sample/assets/test-configs/test-085-text-style-inheritance.json
  • flutter-sample/assets/test-configs/test-086-style-class-usage.json
  • flutter-sample/assets/test-configs/test-087-inline-vs-inherited.json
  • flutter-sample/assets/test-configs/test-088-theme-default-styles.json
  • flutter-sample/assets/test-configs/test-089-styled-product-card.json
  • flutter-sample/assets/test-configs/test-090-styled-profile-card.json
  • flutter-sample/assets/test-configs/test-091-offset-percent-box-basic.json
  • flutter-sample/assets/test-configs/test-092-offset-percent-stack-layers.json
  • flutter-sample/assets/test-configs/test-093-offset-percent-negative.json
  • flutter-sample/assets/test-configs/test-094-offset-percent-overflow.json
  • flutter-sample/assets/test-configs/test-095-offset-percent-zero.json
  • flutter-sample/assets/test-configs/test-096-offset-percent-responsive.json
  • flutter-sample/assets/test-configs/test-097-offset-mixed-units.json
  • flutter-sample/assets/test-configs/test-098-offset-percent-nested.json
  • flutter-sample/assets/test-configs/test-099-offset-percent-with-padding.json
  • flutter-sample/assets/test-configs/test-100-offset-percent-gallery-peek.json
  • flutter-sample/assets/test-configs/test-101-aspect-ratio-square-fixed-width.json
  • flutter-sample/assets/test-configs/test-102-aspect-ratio-16-9-fixed-width.json
  • flutter-sample/assets/test-configs/test-103-aspect-ratio-4-3-fixed-width.json
  • flutter-sample/assets/test-configs/test-104-aspect-ratio-fixed-height.json
  • flutter-sample/assets/test-configs/test-105-aspect-ratio-percent-width.json
  • flutter-sample/assets/test-configs/test-106-aspect-ratio-wrap-content.json
  • flutter-sample/assets/test-configs/test-107-aspect-ratio-match-parent.json
  • flutter-sample/assets/test-configs/test-108-aspect-ratio-extreme-wide.json
  • flutter-sample/assets/test-configs/test-109-aspect-ratio-extreme-tall.json
  • flutter-sample/assets/test-configs/test-110-aspect-ratio-mixed-container.json
  • flutter-sample/assets/test-configs/test-111-combined-aspect-offset-box.json
  • flutter-sample/assets/test-configs/test-112-combined-nested-complex.json
  • flutter-sample/assets/test-configs/test-113-combined-gallery-aspect-peek.json
  • flutter-sample/assets/test-configs/test-114-combined-product-grid.json
  • flutter-sample/assets/test-configs/test-115-combined-showcase-all.json
  • flutter-sample/assets/test-configs/test-116-match-parent-comprehensive.json
  • flutter-sample/assets/test-configs/test-117-wrap-content-comprehensive.json
  • flutter-sample/assets/test-configs/test-118-mixed-special-dimensions.json
  • flutter-sample/assets/test-configs/test-119-match-parent-stack-box.json
  • flutter-sample/assets/test-configs/test-120-wrap-content-constraints.json
  • flutter-sample/assets/test-configs/test-121-16x9-ar-image-text-button.json
  • flutter-sample/assets/test-configs/test-122-1x1-ar-image-badge-rounded.json
  • flutter-sample/assets/test-configs/test-123-9x16-ar-video-caption.json
  • flutter-sample/assets/test-configs/test-124-4x3-ar-text-weights.json
  • flutter-sample/assets/test-configs/test-125-2x1-ar-image-split-button.json
  • flutter-sample/assets/test-configs/test-126-text-font-weights.json
  • flutter-sample/assets/test-configs/test-127-text-font-sizes.json
  • flutter-sample/assets/test-configs/test-128-text-alignment.json
  • flutter-sample/assets/test-configs/test-129-text-decoration-italic.json
  • flutter-sample/assets/test-configs/test-130-text-maxlines-overflow.json
  • flutter-sample/assets/test-configs/test-131-text-gradient.json
  • flutter-sample/assets/test-configs/test-132-image-fit-crop-contain.json
  • flutter-sample/assets/test-configs/test-133-image-gif-rounded.json
  • flutter-sample/assets/test-configs/test-134-image-border-radius.json
  • flutter-sample/assets/test-configs/test-135-images-z-order.json
  • flutter-sample/assets/test-configs/test-136-video-autoplay-muted.json
  • flutter-sample/assets/test-configs/test-137-video-with-controls.json
  • flutter-sample/assets/test-configs/test-138-9x16-video-button.json
  • flutter-sample/assets/test-configs/test-139-button-centered.json
  • flutter-sample/assets/test-configs/test-140-button-primary-secondary.json
  • flutter-sample/assets/test-configs/test-141-button-size-variants.json
  • flutter-sample/assets/test-configs/test-142-cta-card.json
  • flutter-sample/assets/test-configs/test-143-button-rounded-text.json
  • flutter-sample/assets/test-configs/test-144-rounded-box-text.json
  • flutter-sample/assets/test-configs/test-145-nested-rounded-boxes.json
  • flutter-sample/assets/test-configs/test-146-image-overlay-rounded.json
  • flutter-sample/assets/test-configs/test-147-hero-banner-complex.json
  • flutter-sample/assets/test-configs/test-148-product-card-complex.json
  • flutter-sample/assets/test-configs/test-149-notification-card.json
  • flutter-sample/assets/test-configs/test-150-dashboard-widget.json
  • flutter-sample/assets/test-configs/test-151-video-player-card.json
  • flutter-sample/assets/test-configs/test-152-text-corners.json
  • flutter-sample/assets/test-configs/test-153-image-clipped.json
  • flutter-sample/assets/test-configs/test-154-nested-box-deep.json
  • flutter-sample/assets/test-configs/test-155-all-element-types.json
  • flutter-sample/assets/test-configs/test-156-button-backgrounds.json
  • flutter-sample/assets/test-configs/test-157-gallery-box-freeflow-indicators-navbtns.json
  • flutter-sample/assets/test-configs/test-158-gallery-box-freeflow-indicators-only.json
  • flutter-sample/assets/test-configs/test-159-gallery-box-freeflow-navbtns-only.json
  • flutter-sample/assets/test-configs/test-160-gallery-box-freeflow-minimal.json
  • flutter-sample/assets/test-configs/test-161-gallery-box-freeflow-tall-images.json
  • flutter-sample/assets/test-configs/test-162-gallery-box-freeflow-video-items.json
  • flutter-sample/assets/test-configs/test-163-gallery-box-freeflow-button-items.json
  • flutter-sample/assets/test-configs/test-164-gallery-box-freeflow-5items.json
  • flutter-sample/assets/test-configs/test-165-gallery-box-grid2col-indicators-navbtns.json
  • flutter-sample/assets/test-configs/test-166-gallery-box-grid2col-indicators-only.json
  • flutter-sample/assets/test-configs/test-167-gallery-box-grid2col-navbtns-only.json
  • flutter-sample/assets/test-configs/test-168-gallery-box-grid2col-minimal.json
  • flutter-sample/assets/test-configs/test-169-gallery-box-grid3col-indicators.json
  • flutter-sample/assets/test-configs/test-170-gallery-box-grid3col-navbtns.json
  • flutter-sample/assets/test-configs/test-171-gallery-box-grid2col-video.json
  • flutter-sample/assets/test-configs/test-172-gallery-box-grid2col-vertical.json
  • flutter-sample/assets/test-configs/test-172-video-fullscreen-openurl.json
  • flutter-sample/assets/test-configs/test-173-gallery-box-snapping-indicators-navbtns.json
  • flutter-sample/assets/test-configs/test-174-gallery-box-snapping-indicators-only.json
  • flutter-sample/assets/test-configs/test-175-gallery-box-snapping-navbtns-only.json
  • flutter-sample/assets/test-configs/test-176-gallery-box-snapping-minimal.json
  • flutter-sample/assets/test-configs/test-177-html-inline-basic.json
  • flutter-sample/assets/test-configs/test-178-html-with-javascript.json
  • flutter-sample/assets/test-configs/test-179-html-transparent-bg.json
  • flutter-sample/assets/test-configs/test-180-html-scrollable-content.json
  • flutter-sample/assets/test-configs/test-VERIFY-percentage-offset-fix.json
  • flutter-sample/assets/test-configs/test-parity-80pct-ar.json
  • flutter-sample/ios/.gitignore
  • flutter-sample/ios/Flutter/AppFrameworkInfo.plist
  • flutter-sample/ios/Flutter/Debug.xcconfig
  • flutter-sample/ios/Flutter/Release.xcconfig
  • flutter-sample/ios/Podfile
  • flutter-sample/ios/Runner.xcodeproj/project.pbxproj
  • flutter-sample/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  • flutter-sample/ios/Runner/AppDelegate.swift
  • flutter-sample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  • flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  • flutter-sample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  • flutter-sample/ios/Runner/Base.lproj/LaunchScreen.storyboard
  • flutter-sample/ios/Runner/Base.lproj/Main.storyboard
  • flutter-sample/ios/Runner/Info.plist
  • flutter-sample/ios/Runner/Runner-Bridging-Header.h
  • flutter-sample/ios/RunnerTests/RunnerTests.swift
  • flutter-sample/lib/app.dart
  • flutter-sample/lib/main.dart
  • flutter-sample/lib/screens/animation_demo_screen.dart
  • flutter-sample/lib/screens/arrangement_demo_screen.dart
  • flutter-sample/lib/screens/banner_detail_screen.dart
  • flutter-sample/lib/screens/banner_showcase_screen.dart
  • flutter-sample/lib/screens/bridge_integration_screen.dart
  • flutter-sample/lib/screens/clevertap_integration_screen.dart
  • flutter-sample/lib/screens/home_screen_demo.dart
  • flutter-sample/lib/screens/json_viewer_screen.dart
  • flutter-sample/lib/screens/more_menu_screen.dart
  • flutter-sample/lib/screens/other_demos_screen.dart
  • flutter-sample/lib/screens/slot_demo_screen.dart
  • flutter-sample/lib/screens/test_browser_screen.dart
  • flutter-sample/lib/widgets/error_widget.dart
  • flutter-sample/lib/widgets/json_loader.dart
  • flutter-sample/lib/widgets/nd_demo_card.dart
  • flutter-sample/linux/.gitignore
  • flutter-sample/linux/CMakeLists.txt
  • flutter-sample/linux/flutter/CMakeLists.txt
  • flutter-sample/linux/flutter/generated_plugin_registrant.cc
  • flutter-sample/linux/flutter/generated_plugin_registrant.h
  • flutter-sample/linux/flutter/generated_plugins.cmake
  • flutter-sample/linux/runner/CMakeLists.txt
  • flutter-sample/linux/runner/main.cc
  • flutter-sample/linux/runner/my_application.cc
  • flutter-sample/linux/runner/my_application.h
  • flutter-sample/macos/.gitignore
  • flutter-sample/macos/Flutter/Flutter-Debug.xcconfig
  • flutter-sample/macos/Flutter/Flutter-Release.xcconfig
  • flutter-sample/macos/Flutter/GeneratedPluginRegistrant.swift
  • flutter-sample/macos/Podfile
  • flutter-sample/macos/Runner.xcodeproj/project.pbxproj
  • flutter-sample/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  • flutter-sample/macos/Runner/AppDelegate.swift
  • flutter-sample/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  • flutter-sample/macos/Runner/Base.lproj/MainMenu.xib
  • flutter-sample/macos/Runner/Configs/AppInfo.xcconfig
  • flutter-sample/macos/Runner/Configs/Debug.xcconfig
  • flutter-sample/macos/Runner/Configs/Release.xcconfig
  • flutter-sample/macos/Runner/Configs/Warnings.xcconfig
  • flutter-sample/macos/Runner/DebugProfile.entitlements
  • flutter-sample/macos/Runner/Info.plist
  • flutter-sample/macos/Runner/MainFlutterWindow.swift
  • flutter-sample/macos/Runner/Release.entitlements
  • flutter-sample/macos/RunnerTests/RunnerTests.swift
  • flutter-sample/pubspec.yaml
  • flutter-sample/test/widget_test.dart
  • flutter-sample/web/index.html
  • flutter-sample/web/manifest.json
  • flutter-sample/windows/.gitignore
  • flutter-sample/windows/CMakeLists.txt
  • flutter-sample/windows/flutter/CMakeLists.txt
  • flutter-sample/windows/flutter/generated_plugin_registrant.cc
  • flutter-sample/windows/flutter/generated_plugin_registrant.h
  • flutter-sample/windows/flutter/generated_plugins.cmake
  • flutter-sample/windows/runner/CMakeLists.txt
  • flutter-sample/windows/runner/Runner.rc
  • flutter-sample/windows/runner/flutter_window.cpp
  • flutter-sample/windows/runner/flutter_window.h
  • flutter-sample/windows/runner/main.cpp
  • flutter-sample/windows/runner/resource.h
  • flutter-sample/windows/runner/runner.exe.manifest
  • flutter-sample/windows/runner/utils.cpp
  • flutter-sample/windows/runner/utils.h
  • flutter-sample/windows/runner/win32_window.cpp
  • flutter-sample/windows/runner/win32_window.h
  • flutter/LICENSE
  • flutter/lib/clevertap_native_display.dart
  • flutter/lib/src/bridge/native_display_bridge.dart
  • flutter/lib/src/bridge/native_display_config_parser.dart
  • flutter/lib/src/evaluator/variable_evaluator.dart
  • flutter/lib/src/handler/action_attribution_extras.dart
  • flutter/lib/src/handler/action_handler.dart
  • flutter/lib/src/internal/nd_logger.dart
  • flutter/lib/src/internal/nd_sdk_version.dart
  • flutter/lib/src/internal/viewed_units_tracker.dart
  • flutter/lib/src/models/action.dart
  • flutter/lib/src/models/background.dart
  • flutter/lib/src/models/enums.dart
  • flutter/lib/src/models/gallery_config.dart
  • flutter/lib/src/models/layout.dart
  • flutter/lib/src/models/native_display_config.dart
  • flutter/lib/src/models/native_display_node.dart
  • flutter/lib/src/models/native_display_unit.dart
  • flutter/lib/src/models/node_config.dart
  • flutter/lib/src/models/style.dart
  • flutter/lib/src/models/text_dimension.dart
  • flutter/lib/src/renderer/animation_modifier.dart
  • flutter/lib/src/renderer/containers/box_container.dart
  • flutter/lib/src/renderer/containers/gallery_renderer.dart
  • flutter/lib/src/renderer/containers/horizontal_container.dart
  • flutter/lib/src/renderer/containers/vertical_container.dart
  • flutter/lib/src/renderer/elements/button_element.dart
  • flutter/lib/src/renderer/elements/divider_element.dart
  • flutter/lib/src/renderer/elements/html_element.dart
  • flutter/lib/src/renderer/elements/image_element.dart
  • flutter/lib/src/renderer/elements/spacer_element.dart
  • flutter/lib/src/renderer/elements/text_element.dart
  • flutter/lib/src/renderer/elements/video_element.dart
  • flutter/lib/src/renderer/native_display_renderer.dart
  • flutter/lib/src/renderer/native_display_view.dart
  • flutter/lib/src/renderer/notification_click_scope.dart
  • flutter/lib/src/renderer/resolved_styles_scope.dart
  • flutter/lib/src/renderer/root_height_scope.dart
  • flutter/lib/src/renderer/style_applier.dart
  • flutter/lib/src/renderer/unit_id_scope.dart
  • flutter/lib/src/style/style_resolver.dart
  • flutter/lib/src/utils/color_parser.dart
  • flutter/lib/src/utils/dimension_calculator.dart
  • flutter/pubspec.yaml
  • flutter/test/bridge/native_display_bridge_test.dart
  • flutter/test/business_logic_test.dart
  • flutter/test/handler/action_attribution_extras_test.dart
  • flutter/test/handler/action_handler_test.dart
  • flutter/test/internal/nd_logger_test.dart
  • flutter/test/models/models_test.dart
  • flutter/test/renderer/animation_modifier_test.dart
  • flutter/test/renderer/box_hit_test_test.dart
  • flutter/test/renderer/button_spacer_divider_test.dart
  • flutter/test/renderer/container_test.dart
  • flutter/test/renderer/html_element_test.dart
  • flutter/test/renderer/image_element_test.dart
  • flutter/test/renderer/native_display_attribution_test.dart
  • flutter/test/renderer/native_display_view_test.dart
  • flutter/test/renderer/style_applier_test.dart
  • flutter/test/renderer/text_element_test.dart
  • flutter/test/renderer/video_controls_tap_test.dart
  • flutter/test/renderer/video_element_test.dart

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/SDK-5821-flutter-models

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 9 commits June 26, 2026 16:54
Create the flutter/ plugin package with pubspec.yaml, barrel export, and
the complete Dart model layer. This is the foundation for the Flutter
renderer — all subsequent steps depend on these models.

Files created:
- flutter/pubspec.yaml (sdk: >=3.0.0, flutter: >=3.10.0, cached_network_image ^3.4.1)
- flutter/lib/clevertap_native_display.dart (barrel export)
- flutter/lib/src/models/enums.dart (22 enums, lowercase JSON values, Dart 3.0 switch)
- flutter/lib/src/models/layout.dart (Dimension, NDOffset, Spacing, ChildArrangement, Layout)
- flutter/lib/src/models/text_dimension.dart (TextDimension with /1000 percent resolution)
- flutter/lib/src/models/style.dart (Style with merge() + cascadingOnly())
- flutter/lib/src/models/background.dart (Background sealed class, 10 subtypes)
- flutter/lib/src/models/action.dart (NDAction sealed class, 5 subtypes, ActionTriggers)
- flutter/lib/src/models/gallery_config.dart (GalleryConfig, PeekConfig, IndicatorStyle, ArrowStyle)
- flutter/lib/src/models/node_config.dart (ImageConfig, HtmlConfig, DividerConfig, NDAnimation)
- flutter/lib/src/models/native_display_node.dart (NativeDisplayNode sealed, Container, Element)
- flutter/lib/src/models/native_display_config.dart (NativeDisplayConfig, ResolvedConfig, NDTheme, StyleClass)
- flutter/test/models/models_test.dart (51 tests — all pass)

Key implementation details:
- Manual fromJson/toJson (no code generation)
- Dart 3.0 pattern matching throughout (switch expressions)
- Style.merge(): this > other via null coalescing per field
- Style.cascadingOnly(): text properties + opacity only
- Spacing.resolveTop/Bottom/Left/Right: top ?? vertical ?? all ?? 0.0
- TextDimension percent resolution: rootHeight * value / 1000
- Background sealed class: 5 v1 types fully implemented; 6 v2 types parsed with warning
- OpenUrlAction: handles both plain string and platform map {"ios": ..., "android": ...}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ser, DimensionCalculator (Flutter)

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ButtonElement: GestureDetector + StyleApplier wrapping Center(Text); fires actionListener on onClick action
- SpacerElement: match_parent → Spacer(); fixed dimensions → SizedBox
- DividerElement: horizontal/vertical SizedBox+ColoredBox; hides Flutter Orientation to resolve name conflict with ND Orientation enum
- NativeDisplayRenderer: dispatch BUTTON/SPACER/DIVIDER in _buildElement
- Tests: 9 tests covering all three elements

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uto-scroll (Flutter)

- GalleryRenderer converted from stub StatelessWidget to full StatefulWidget
- PageView.builder with viewportFraction from effectiveItemsPerView
- PageController + Timer for auto-scroll; both disposed in dispose()
- infiniteScroll: null itemCount + modulo index into children list
- Page spacing via symmetric Padding around each child
- Dot indicators rendered as Container rows/columns inside Stack when showIndicators is true
- Hides Flutter Orientation to resolve name conflict with ND Orientation enum
- container_test.dart: replaced stub test with 4 GalleryRenderer tests; upgraded wrap() with RootHeightScope + ResolvedStylesScope

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CTLalit and others added 14 commits June 26, 2026 16:54
- Add video_player: ^2.9.2 dependency
- VideoElement: StatefulWidget initialising VideoPlayerController.networkUrl
- Reads autoPlay, loop, muted, showControls from node bindings (string values)
- Sets looping and volume after controller.initialize()
- Shows play/pause icon overlay when showControls is true; tap toggles playback
- Renders SizedBox.shrink when url is empty or controller not yet initialised
- NativeDisplayRenderer: dispatch ElementType.video to VideoElement
- Tests: 6 boolean-binding unit tests + 2 widget smoke tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add webview_flutter: ^4.9.0 dependency
- HtmlElement: StatefulWidget initialising WebViewController in initState()
- html binding loaded via loadHtmlString (with optional baseUrl); url binding via loadRequest
- html binding takes priority over url when both are present
- javascriptEnabled / transparentBackground read from HtmlConfig
- wrap_content height logs warning and falls back to 200dp; match_parent allows parent to constrain
- NativeDisplayRenderer: dispatch ElementType.html to HtmlElement; remove now-unreachable wildcard case (all 7 ElementType values are now handled)
- Tests: 6 configuration logic tests for bindings and HtmlConfig parsing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AnimationModifier: StatefulWidget with SingleTickerProviderStateMixin
- Resolves AnimationType to FadeTransition, SlideTransition, ScaleTransition combinations:
  fadeIn → Fade; slideIn* → Slide; scaleIn → Scale; fadeScaleIn → Fade+Scale; fadeSlideIn → Fade+Slide
- Slide begin offsets per direction: left=(-1,0), right=(1,0), top=(0,-1), bottom=(0,1)
- Easing resolved to Flutter Curves; spring → Curves.elasticOut
- Delay via Future.delayed before controller.forward()
- NativeDisplayRenderer: wraps every built node in AnimationModifier when node.animation is non-null and type != none
- Tests: 7 tests covering all major animation type combinations and completion

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add url_launcher: ^6.3.0 dependency
- ActionHandler: const class with optional NativeDisplayActionListener
- OpenUrlAction: launches URL via launchUrl; openInBrowser=true uses externalApplication mode
- CustomAction: forwards key, value, metadata to listener as flat params map
- NavigateAction: forwards destination + params to listener
- TrackEventAction: forwards eventName + properties to listener
- CompositeAction: sequential iterates actions in order; parallel uses Future.wait
- Export ActionHandler from clevertap_native_display.dart public API
- Tests: 6 listener-dispatch tests (custom, navigate, event, composite, no-listener guards)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… plugin stubs (Flutter)

- NativeDisplayBridge.dart: static MethodChannel wrapper for com.clevertap.flutter.nativedisplay
  - fetchConfig(unitId): invokes fetchDisplayUnit, parses JSON into NativeDisplayConfig, returns null on PlatformException
  - pushViewedEvent(unitId): invokes pushViewedEvent
  - pushClickedEvent(unitId, elementId?): invokes pushClickedEvent; omits elementId key when null
- Android: CleverTapNativeDisplayPlugin.kt — FlutterPlugin + MethodCallHandler; NativeDisplayPluginBridge interface for host-app delegation
- iOS: CleverTapNativeDisplayPlugin.swift — FlutterPlugin; NativeDisplayPluginBridge protocol for host-app delegation
- Export NativeDisplayBridge from clevertap_native_display.dart public API
- Tests: 6 mock-MethodChannel tests covering fetchConfig (null, parse, PlatformException), pushViewedEvent, pushClickedEvent (with/without elementId)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SourceKit cannot resolve 'import Flutter' outside a full Xcode/Flutter
build context. The standard fix for Flutter plugin files is the
canImport guard. UIKit was unused and removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 4-tab bottom nav (Banners, Browser, Demos, More) mirroring Android/iOS apps
- BannerShowcaseScreen: scrollable list of 10 banner cards with detail navigation
- BannerDetailScreen: full-screen single banner display
- ArrangementDemoScreen: interactive chip strip switching all 7 strategies
- AnimationDemoScreen: 3 animation demos with info card, keyed for rebuild
- HomeScreenDemo: loads home_screen.json with action/component listeners
- OtherDemosScreen: tabbed view (Home, Gallery, E-commerce, Social, Dashboard)
- BridgeIntegrationScreen: loads mock product/notification configs, shows API snippet
- SlotDemoScreen: native display units interleaved with mock feed items
- TestBrowserScreen: prev/next navigation across 181 test-config filenames
- JsonViewerScreen: scrollable raw JSON with copy-to-clipboard
- MoreMenuScreen: list navigation to all demo screens
- Assets: 10 banner JSONs + 12 config JSONs copied from android-sample
- flutter analyze: no issues

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

Android (flutter-sample/android/):
- SampleApplication: init CleverTap, bind NativeDisplayBridge, register listener
- MainActivity: EventChannel (native→Dart units_updated), MethodChannel (pushEvent)
- AndroidManifest: CT account ID/token/region meta-data, INTERNET permission
- build.gradle.kts: CT core SDK 8.0.0 + native-display-sdk dependencies

iOS (flutter-sample/ios/):
- AppDelegate: CleverTap.autoIntegrate(), NativeDisplayBridge bind + fetch
- Podfile: CleverTap-iOS-SDK ~>7.0, CleverTapNativeDisplay :path

Dart (flutter-sample/lib/):
- CleverTapIntegrationScreen: subscribe NativeDisplayBridge.eventStream,
  parse units_updated events, render via NativeDisplayView, Send Event → pushEvent
- app.dart: bottom nav Events/Slots/Browser/More matching Android structure
- SlotDemoScreen: 15 feed items + 4 dashed-border slot placeholders
- Lint fixes: prefer_const_constructors / prefer_const_literals

flutter/ plugin:
- android/build.gradle + AndroidManifest.xml: plugin Android library scaffold
- NativeDisplayBridge: pushEvent (sample MethodChannel) + eventStream (EventChannel)
- gallery_renderer.dart: LayoutBuilder fallback height for unbounded PageView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…op composite build

Problem: flutter-sample referenced clevertap-native-ui-kit (compiled Kotlin 2.1.0)
via includeBuild, forcing Kotlin 2.1.0 + AGP 8.9.1 + Gradle 8.11.1 on the sample.

Fix: remove includeBuild and the native-display-sdk dependency entirely. Subscribe
to display unit callbacks via CleverTapAPI.setDisplayUnitListener(DisplayUnitListener)
from clevertap-android-sdk:8.0.0 (Java interface — no Kotlin binary compat issue).
Forward CleverTapDisplayUnit.getJsonObject().toString() directly to Flutter via
EventChannel. Restores original Flutter scaffold versions: Kotlin 1.8.22, AGP 8.7.0,
Gradle 8.10.2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…play

Two bugs prevented display units from rendering in CleverTapIntegrationScreen:

1. Wrong JSON format: CleverTapDisplayUnit.getJsonObject() returns raw CT JSON
   (type:advanced-builder, slot_id, wzrk_id, etc.) not NativeDisplayConfig JSON.
   Flutter's NativeDisplayConfig.fromJson() cannot parse this format.
   Fix: extract NativeDisplayConfig from the raw CT JSON using the same 3-strategy
   detection as NativeDisplayConfigParser:
   - native_display_config top-level key (dashboard format)
   - custom_kv.nd_config string value
   - root top-level key (direct NativeDisplayConfig JSON)

2. Timing race: display units can arrive from CT server before Flutter subscribes
   to the EventChannel, causing them to be silently dropped (eventSink is null).
   Fix: cache the last received unit JSONs in SampleApplication.cachedUnitJsons
   and replay them immediately in EventChannel.StreamHandler.onListen().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…utter sample platform targets

## Flutter SDK — renderer fixes

- native_display_view: aspectRatio now takes precedence over width.percent — when AR is
  set on a node the renderer uses full available parent width and derives height =
  parentWidth / aspectRatio, matching Android (Compose modifier ordering) and iOS (guard
  check in resolveRootWidth). _effectiveWidth returns availableWidth when AR > 0;
  _applyRootSizing short-circuits immediately when AR is present.
- native_display_view: fix _computeRootHeight for percent height roots — previously fell
  through to screenHeight, inflating percent-based fonts by up to 67 %.
- native_display_renderer: remove debugLabel field and all _wrapWithSizing debugPrint calls.
- box_container: remove _buildChild debugPrint.
- vertical_container / horizontal_container: promote from v1 stubs to full arrangement
  implementations — all ArrangementStrategy values (spaced, space_between, space_evenly,
  space_around, start, center, end) now produce correct Column/Row with spacers or
  MainAxisAlignment.
- button_element: propagate maxLines, overflow, and softWrap from resolved style.
- style_applier: fix border resolution — require width > 0 before rendering; skip zero-
  width borders rather than rendering a phantom 1 dp default.

## Flutter sample app

- clevertap_integration_screen: remove debug red border, outer LayoutBuilder, and
  [SAMPLE] log prints; clean up raw JSON logging from onUnitsLoaded.
- test_browser_screen: add test-parity-80pct-ar.json to browser list.
- Add 180+ test-config JSON assets for the in-app test browser.
- Add multi-platform scaffolding (linux, macos, web, windows).
- pubspec: add clevertap_plugin dependency; update flutter-sdk path reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…knowledge bases

Codifies the cross-platform rule discovered while debugging the Flutter renderer:
when aspectRatio is set on a node, it takes full available parent width and derives
height = parentWidth / aspectRatio. Any width.percent (or height.percent) value is
ignored. The rule is identical on all three platforms:

- Android: Compose applies aspectRatio() modifier before fillMaxWidth(fraction); the
  AR modifier locks minW=parentWidth, making fillMaxWidth ineffective.
- iOS: resolveRootWidth() guard returns parentWidth when aspectRatio > 0, regardless
  of percent. Documented in NativeDisplayRenderer.swift line 244.
- Flutter: _effectiveWidth returns availableWidth when layout.aspectRatio > 0;
  _applyRootSizing short-circuits when AR is set.

aspectRatio is skipped only when BOTH width AND height are simultaneously fixed (dp/sp/px).
Percent dimensions are never treated as "fixed" for this check.

Files updated:
- CLAUDE.md — layout system section
- .claude/reference/CLAUDE_CODE_REFERENCE_ACTUAL.md — layout model + priority table
- .claude/reference/JSON_STRUCTURE_REFERENCE.md — How it works + Priority Rules sections
- docs/JSON_STRUCTURE_REFERENCE.md — same (kept in sync)
- docs/BACKEND_PAYLOAD_SPEC.md — per-platform mechanism table
- .claude/agents/android-sdk/knowledge/rendering-pipeline.md — Stage 4 precedence rule
- .claude/agents/android-sdk/knowledge/gotchas.md — new gotcha entry
- .claude/agents/ios-sdk/knowledge/architecture.md — Swift code evidence
- .claude/agents/flutter-sdk/knowledge/rendering-pipeline.md — root sizing implementation
- .claude/agents/flutter-sdk/knowledge/architecture.md — NativeDisplayView description
- .claude/agents/testing/knowledge/json-generation-rules.md — Section 6 Aspect Ratio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add NativeDisplayUnit model (unitId, slotId, config, customExtras, resolvedStyles)
- Add NativeDisplayConfigParser with tryParse/parseAll running off main thread
  via Isolate.run(); falls back to synchronous on Dart < 3.4
- Mirrors 3-strategy extraction from Android NativeDisplayConfigParser.kt:
  native_display_config key → custom_kv.nd_config string → root key
- NativeDisplayView accepts optional pre-resolved resolvedStyles to skip
  redundant StyleResolver calls when using NativeDisplayConfigParser
- Export both new types from the public barrel
- Simplify clevertap_integration_screen.dart: remove _deepCast, _extractEntry,
  _UnitEntry; replace with NativeDisplayConfigParser.parseAll() (4 lines)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NativeDisplayView: StatelessWidget → StatefulWidget; StyleResolver.resolveAll()
  now runs once in initState/didUpdateWidget, not on every parent rebuild;
  added fixed-dimension shortcut that bypasses LayoutBuilder when root has
  only dp dimensions (no percent, no aspectRatio)
- style_applier.dart: for solid backgrounds, bake opacity into Color.withValues()
  instead of wrapping the whole subtree in Opacity widget (avoids saveLayer)
- image_element.dart: replace Image.network() with CachedNetworkImage for static
  images (GIFs keep Image.network for animation fidelity); wrap in RepaintBoundary
- video_element.dart: wrap in RepaintBoundary to isolate frame repaints
- gallery_renderer.dart: wrap each page item in RepaintBoundary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@CTLalit
CTLalit force-pushed the feat/SDK-5821-flutter-models branch from 507575c to cbe9f08 Compare June 29, 2026 08:41
@akashvercetti

Copy link
Copy Markdown

@CTLalit I think we can do without the Kotlin/Swift bridge here. Our CT Flutter clevertap_plugin sdk already exposes all of this in pure Dart. We already have these methods there: getDisplayUnitForId, pushDisplayUnitViewedEvent, pushDisplayUnitClickedEvent, and pushDisplayUnitElementClickedEvent(unitId, extras)

We could call those directly from your dart code and delete the native kotlin/swift brridgecode, same as we did on React Native ND SDK. So the renderer stays pure Dart and we can call the existing Flutter CT plugin for Core SDK methods.

Try a CT Flutter sdk version that has the ND element-clicked method.

…acker, UnitIdScope

Add the Flutter counterparts of the shipped Android/iOS attribution and
logging work:

- ViewedUnitsTracker: process-singleton gate for per-impression
  "Notification Viewed" so widget rebuilds/recycling don't refire.
- NDLogger: structured, client-configurable logger mirroring Core SDK
  debug levels (OFF/INFO/DEBUG/VERBOSE) and the native NDLogger format.
- UnitIdScope: InheritedWidget that propagates wzrk_id to element
  renderers so click/view attribution needs no manual threading.

Wires element-click attribution through action.dart / action_handler and
the button/image/video element renderers.

Mirrors Android/iOS: SDK-5860 (NDLogger), SDK-5883 (per-impression
Viewed), SDK-5862 (image-tap Clicked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CTLalit and others added 6 commits August 13, 2026 11:23
…ked bridge (#77)

Bring Flutter click/view attribution to parity with the shipped Android/iOS
work (SDK-5793, SDK-5846). Previously only unitId + elementId reached Core
SDK; the clicked action's context and the ND SDK version stamp were dropped.

- ActionAttributionExtras: pure Dart helper that flattens an NDAction into a
  flat extras map (action_type + reserved action_* keys, spread metadata/
  params/properties and CustomAction JsonObject value entries, action_value
  for scalars). sanitize() drops null/empty-key entries and stamps the ND SDK
  version (nd_lib_v_name/nd_lib_v_code). Mirrors the shipped Kotlin/Swift
  helper key-for-key. Client never injects wzrk_* (server-owned namespace).
- NDSdkVersion: version constants kept in sync with pubspec (0.1.0 / 100),
  mirroring Android BuildConfig / iOS NativeDisplaySDKVersion.
- NativeDisplayBridge.pushClickedEvent gains an optional extras map, forwarded
  as additionalProperties when non-empty; pushViewedEvent always forwards the
  version stamp (impressions are unit-level, no caller extras).
- Button + IMAGE call sites build extras from the resolved onClick action.
- Native plugins (Android Kotlin + iOS Swift): NativeDisplayPluginBridge
  pushClickedEvent/pushViewedEvent widened to carry additionalProperties;
  read from the MethodCall and forwarded to the host bridge (which forwards
  to the ND SDK's element-clicked / viewed-with-extras path).
- Tests: 20 ActionAttributionExtras cases + bridge arg-shape assertions.

flutter analyze clean; flutter test 183/183 pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…78)

The Flutter SDK's NDLogger ignored the host Core SDK's configured debug
level — NDLogger.syncFromCoreSdk existed but nothing called it. Align
Flutter with the shipped Android/iOS CleverTapAutoWire behaviour (SDK-5860).

- NativeDisplayBridge.syncLogLevelFromCoreSdk(): reads the Core debug level
  over the platform channel (new getCoreDebugLevel method) and forwards it to
  NDLogger.syncFromCoreSdk. No-op once the client called setLogLevel
  (client-wins via NDLogger.explicitlySet); graceful no-op when no bridge /
  Core SDK is wired (null or PlatformException leaves the level intact).
- Lazily triggered once per app run from the first fetchConfig (fire-and-forget
  so config fetch never blocks); also public so the host can call it right
  after wiring.
- Native plugins: NativeDisplayPluginBridge gains getCoreDebugLevel(): Int?
  (Android, CleverTapAPI.getDebugLevel()) / coreDebugLevel() -> NSNumber?
  (iOS, KVC debugLevel). iOS handles the arg-less method before the args guard.
- NDLogger.resetForTest() (@VisibleForTesting) for test isolation of the
  process-global logger state; no change to syncFromCoreSdk/setLevel logic.
- Tests: NDLogLevel.fromInt mapping (incl. unknown→DEBUG) + syncFromCoreSdk
  semantics + client-wins + null/exception no-op bridge cases.

flutter analyze clean; flutter test 192/192 pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Android/iOS #54 fixed BOX hit-testing, video-controls tap reliability,
numeric-boolean evaluation, and non-string JSON binding values. The Flutter
renderer already meets this parity (Stack fall-through hit-testing,
HitTestBehavior.opaque video overlay with no stale timer, num!=0 + "1.0"
coercion). This locks it in with regression tests — NO behaviour change,
zero non-test source touched.

- box_hit_test_test.dart: non-interactive sibling stacked over an interactive
  child does not swallow the tap; topmost interactive child wins (z-order).
- video_controls_tap_test.dart: controls overlay uses HitTestBehavior.opaque,
  tap toggles play/pause (via a fake VideoPlayerPlatform), showControls:false
  renders no overlay.
- business_logic_test.dart: numeric-boolean literals + raw-JSON variable values
  coerce to bool; non-string binding JSON values coerce to string without
  throwing.

flutter analyze clean; flutter test 215/215 pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix two adoption/packaging gaps found while auditing minimum-version
requirements.

- Add flutter/ios/clevertap_native_display.podspec — the plugin shipped with
  only Classes/*.swift and no podspec, so CocoaPods could not integrate it and
  iOS adoption was impossible. Minimal bridge pod: depends on Flutter only (no
  CleverTap SDK — the host provides Core SDK and wires the bridge), iOS 12.0
  deployment target, Classes/**/* sources, Swift 5.
- Add flutter/LICENSE (MIT, copied from repo root) so the pub package root and
  the podspec's license reference are self-contained.
- pubspec.yaml: raise the declared floor from Dart 3.0 / Flutter 3.10 to
  Dart 3.6 / Flutter 3.27 — the honest minimum. Dominated by compileSdk 36
  (kept current for Play Store) → AGP 8.7 + suppressUnsupportedCompileSdk →
  Gradle 8.7+/JDK 17, first available with Flutter 3.27. Prior floor let
  pub get succeed on toolchains that then failed at Gradle time.

Kotlin 2.x confirmed NOT required (plugin pins no Kotlin; sample builds on
1.8.22). CleverTap core clevertap_plugin (Dart >=2.12 / Flutter >=1.17) is not
a limiter. compileSdk stays 36 by design.

flutter pub get resolves; flutter analyze clean; podspec syntax validated.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ient (#82)

Attribution now flows through the host's clevertap_plugin Dart APIs, so the
ND plugin's native side is removed entirely and the plugin becomes pure Dart.
Supersedes the native portions of SDK-6009/6010/6013.

- Remove flutter/android/ and flutter/ios/ (native plugin + the SDK-6013
  podspec); drop the flutter.plugin.platforms block from pubspec — now a
  regular Flutter/Dart package. Keep flutter/LICENSE.
- NativeDisplayBridge slimmed to a pure-Dart helper: setLogLevel(NDLogLevel)
  and syncLogLevelFromCore(int) → NDLogger.syncFromCoreSdk (client-wins guard
  intact). All MethodChannel/EventChannel usage and fetchConfig/eventStream/
  pushEvent/pushViewed/pushClicked/getCoreDebugLevel removed.
- Attribution surfaced to the host via NativeDisplayView callbacks:
  onNotificationViewed(unitId) — fired once per impression via ViewedUnitsTracker;
  onNotificationClicked(unitId, elementId, extras) — extras built by
  ActionAttributionExtras. New NotificationClickScope (mirrors UnitIdScope) lets
  elements read the callback from context. Host feeds these into
  clevertap_plugin pushDisplayUnitViewedEvent / pushDisplayUnitElementClickedEvent.
- Re-lower the version floor: with compileSdk 36 gone, the binding constraint is
  webview_flutter 4.9.0 → Flutter 3.24 / Dart 3.5 (down from SDK-6013's 3.27).
- Tests: replace native arg-shape tests with pure-Dart log-level tests; add
  native_display_attribution_test.dart (viewed once/impression, click extras).
- gitignore the pub-generated flutter/.flutter-plugins* registrants.

flutter pub get resolves; flutter analyze clean; flutter test 213 pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…83)

* SDK-6020: Wire flutter-sample attribution via clevertap_plugin 4.1.0

Bring the Flutter sample to parity with the Android/iOS samples using the
pure-Dart SDK's client-surfaced attribution callbacks (SDK-6019).

- Bump clevertap_plugin ^4.0.0 → ^4.1.0 (adds pushDisplayUnitElementClickedEvent).
- clevertap_integration_screen.dart (live-unit screen): wire NativeDisplayView
  onNotificationViewed → CleverTapPlugin.pushDisplayUnitViewedEvent(unitId) and
  onNotificationClicked → pushDisplayUnitElementClickedEvent(unitId, extras),
  with pushDisplayUnitClickedEvent(unitId) as the no-elementId fallback. Drop the
  old manual unit-level click from the action listener. Event log now shows
  VIEWED/CLICKED attribution lines.
- main.dart: NativeDisplayBridge.setLogLevel(NDLogLevel.debug) log-level demo.
- bridge_integration_screen.dart: replace the deleted-API doc-snippet with the
  real clevertap_plugin model (getAllDisplayUnits → parse → NativeDisplayView +
  attribution callbacks); relabel "Bridge API" → "Integration Pattern".
- slot_demo_screen.dart: fix a stale NativeDisplayBridge comment.

Asset-only screens (bridge mock, test browser, slots) intentionally not wired —
no real unitId. Credentials left as-is (hygiene decision pending).

flutter analyze clean.

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

* SDK-6020: replace sample CleverTap credentials with placeholders

The committed sample no longer ships live account credentials — clients
populate YOUR_CLEVERTAP_ACCOUNT_ID/TOKEN/REGION. Matches the Android/iOS
samples. Local development creds are kept out of VCS via skip-worktree.

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

---------

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.

2 participants