flutter_salesforce_personalization 2.0.0 copy "flutter_salesforce_personalization: ^2.0.0" to clipboard
flutter_salesforce_personalization: ^2.0.0 copied to clipboard

A Flutter plugin that wraps the native iOS/Android Salesforce Personalization SDKs to deliver personalized UI components (Hero Banner, Recommendations) in Flutter apps.

Changelog #

All notable changes to this project are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

2.0.0 #

The first release since 1.0.0. It renames the public out-of-the-box type surface — a breaking change, which is what drives this major version bump — and adds a first-class engagement-tracking API on ComponentContext.

Added #

Added engagement tracking on ComponentContext so custom component authors no longer inspect engagementPayloads directly:

  • context.trackEngagement(action) — single-element components (Banner). Never deduped; fires every call.

  • context.trackEngagementPerItem(index, action) — list components (Recommendations). Never deduped; fires every call. Renamed from trackEngagementForItem (no behavior change beyond the name).

  • context.trackEngagementViewOnce([action = 'View']) / context.trackEngagementViewOncePerItem(index, [action = 'View']) — once-per-serving reporting.

    These dedup — use them only for View or another action that is genuinely once-per-serving. Never use them for Click or any repeatable action: every call after the first for a given serving is silently dropped.

    Both guarantee action fires at most once per ComponentContext instance, even across widget unmount/remount recycles that reuse that same instance. Because that dedup keys on the context object's own identity (not personalizationId), the primitive alone is safe to call straight from build() — including a StatelessWidget's — with no lifecycle wiring, but it re-fires on a fresh ComponentContext instance (e.g. from controller.refresh(), or a production → preview transition that re-fetches) even if the new instance happens to carry the same personalizationId — instance identity and personalizationId are independent signals, and every successful fetch mints a new instance regardless of whether the id changed.

    Layered on top, within a living widget State, the OOTB widgets (HeroBanner, RecsCarousel) and the recommended custom-component pattern pair this primitive with a per-index personalizationId "memo": View fires once per personalizationId, not just once per instance — a same-PID refetch does not refire, a new PID does. A fully disposed-and-remounted ContentZone (e.g. an outer virtualized list dropping it offscreen and rebuilding it) starts a new State with no memo of its own — that's a genuine new serving and correctly refires; this is the intended, narrowed contract. HeroBanner calls context.trackEngagementViewOnce() on mount and again only when personalizationId changes; each RecsCarousel card calls context.trackEngagementViewOncePerItem(index) in build(), gated by the carousel State's PID memo. See doc/VIEW_ONCE_TRACKING.md for the full treatment, including the PID-memo pattern and the same-PID re-serve edge case (a re-serve at an already-memoed PID does not refire, even if an earlier serving at that PID lacked usable payloads and the later one now has them — this is intended behavior).

    The example app's custom components demonstrate the pattern. MiniRecsComponent (_MiniRecsList) is a StatefulWidget holding the same per-index PID memo as RecsCarousel — the primitive + memo exemplar for build-time tracking. CoverflowRecommendations tracks View from onPageChanged (plus the initial page from initState, since PageView.builder builds cached, currently-offscreen neighbors and onPageChanged doesn't fire for page 0), gated by the same PID-memo pattern — the visibility-driven exemplar. Both fire Click on every tap via the public trackEngagementPerItem.

All four methods resolve the payload for the given action (matched case-insensitively, whitespace-trimmed), apply a consistent logging policy, and are fire-and-forget — they never throw and are a safe no-op when no matching payload exists. Added an optional componentName field to ComponentContext used to tag diagnostic log messages. The well-known action names EngagementActions.view and EngagementActions.click are exported for discoverability; the track methods still accept any string.

Engagement diagnostic logging is driven by cause rather than content source: missing or wrong-shape payloads log at DEBUG (expected in mock/preview and can fire per frame); an action not found on a correctly-shaped payload logs at WARN (actionable misconfiguration). The OOTB Banner and Recommendations widgets route all tracking through the ComponentContext methods above. The one-time-per-fetch shape-mismatch validation log (distinct from the per-track DEBUG path above) is WARN, matching the existing count-mismatch log. The OOTB widgets' diagnostic log tag is the backend-resolved componentName (e.g. Salesforce_Banner, Salesforce_Recommendations).

Changed #

Renamed the out-of-the-box (OOTB) component classes and their associated model, style, and event types to carry a Salesforce prefix, aligning the public type surface with the backend component API names (Salesforce_Banner / Salesforce_Recommendations). Any app importing the old names must update its imports, constructor calls, and type annotations.

Old New
BannerComponent SalesforceBanner
Recommendations SalesforceRecommendations
BannerModel SalesforceBannerModel
RecommendationsModel SalesforceRecommendationsModel
RecommendationItem SalesforceRecommendationItem
RecommendationTapEvent SalesforceRecommendationTapEvent
BannerStyle SalesforceBannerStyle
RecommendationsStyle SalesforceRecommendationsStyle
SectionHeaderStyle SalesforceSectionHeaderStyle
RecsCardStyle SalesforceRecommendationCardStyle

Not affected: the backend component-name string constants ('Salesforce_Banner' / 'Salesforce_Recommendations') and internal log tags are unchanged, so no backend or mock-content configuration changes are required.

  • Upgraded the bundled native Personalization SDK from 2.x to 3.x. The plugin's native dependency moved a full major: iOS Salesforce-Personalization ~> 2.0~> 3.0, Android com.salesforce.personalization:sdk 2.+3.+. Host apps pick these up transitively (they are not declared directly), but must be able to resolve the 3.x artifacts — the same Salesforce GitHub Pages Maven repos on Android and CocoaPods on iOS; see doc/Android.md and doc/iOS.md. Both constraints deliberately float within the 3.x major (~> 3.0, 3.+) rather than pinning an exact release, so host apps receive native patch/minor fixes without waiting on a plugin release; the plugin is validated against the current 3.x line at publish time. Apps that require byte-reproducible builds should pin the exact resolved native version in their own lockfile (Podfile.lock / Gradle dependency locking), which is where build reproducibility belongs — not in a library's dependency range.
  • Lowered the toolchain floor to the verified minimums, reducing version conflicts for host apps. Dart ^3.3.0 (was ^3.5.0), Flutter >=3.19.0 (was >=3.24.0), Kotlin Gradle plugin 2.3.0 (was 2.4.0), Gradle wrapper 8.13 (was 8.14), and Swift 5.7 (was 5.9). No source changes were needed; these were empirically confirmed to build green on both platforms. compileSdk 37, AGP 8.13.2, and iOS 15.0 remain hard floors imposed by the native Personalization SDK 3.x.

Removed #

Removed the low-level engagement export that 1.0.0 briefly exposed. EngagementPayload, the EngagementPayloads* types, and the direct EngagementPayloads.trackEngagement(payload) entry point are no longer part of the public API. Custom component authors track engagement through the ComponentContext methods added above, using the ComponentContext the zone hands to build(model, context) (do not construct or cache your own).

Migration: replace EngagementPayloads.trackEngagement(payload) with context.trackEngagement(action) (single element) or context.trackEngagementPerItem(index, action) (list); for once-per-serving View, use context.trackEngagementViewOnce() / context.trackEngagementViewOncePerItem(index).

Fixed #

ContentZone widget-lifecycle correctness for dynamic reconfiguration:

  • allowedComponents changes are now honored on rebuild — didUpdateWidget rebuilds the allowed-name list and component lookup and diffs by value, so a changed component set is picked up in place (no remount), while an unchanged one never triggers a redundant refetch.
  • A same-name component-instance swap re-renders in place from the retained serving JSON, reusing the same ComponentContext instance — no network refetch and no engagement View refire. A swap whose validator rejects or throws surfaces the fallback (recording lastRefreshError) without a fetch, and the previously resolved serving is preserved so a later compatible swap recovers in place.
  • Controller unbinding is ownership-aware — a zone clears only the controller binding it actually installed, so swapping a shared controller no longer tears down another zone's binding.

1.0.0 — Initial public release #

First public release of the Salesforce Personalization Flutter plugin — a Flutter wrapper over the native iOS and Android Personalization SDKs for rendering personalized content and tracking engagement.

Features #

  • ContentZone — widget that fetches personalized content from the native SDK and renders the matching component, with loading and fallback builders, a configurable timeoutMs, and optional DecisionsRequestContext for anchor/attribute biasing.
  • Out-of-the-box componentsBannerComponent and Recommendations, each with optional style overrides and tap handlers.
  • Custom components — implement Component<T extends ComponentModel> to render your own backend transformers, or override an OOTB component by reusing its name.
  • Event tracking — single PersonalizationModule.track(...) entry point with sealed event types: CustomEvent, CartEvent, OrderEvent, and CatalogObjectEvent.
  • Engagement tracking — OOTB BannerComponent and Recommendations auto-track View (on mount and on every personalizationId change) and Click (on tap) for all content sources. At the time of this release, custom-component authors could call EngagementPayloads.trackEngagement(payload) directly; that low-level API was later removed from the public export surface (see 2.0.0 above) in favor of ComponentContext.trackEngagement / trackEngagementPerItem.
  • Identity — profile ID, profile attributes (upsert / clear), and party identification, plus read APIs for current state.
  • ConsentsetConsent / isConsentOptIn.
  • Preview — QR-code / deep-link preview mode via handlePreviewUrl.
  • Pull-to-refreshContentZoneController for programmatic refresh, with silent and loading-state modes.
  • Offline / design-timeMockDataContentZone<T> renders caller-supplied mock content with no network or native SDK.

Configuration #

The host app initializes the native SDK; the plugin does not expose a Dart-side configure(...). Provide the CDP appId, endpoint, and cdnUrl to the native layer — the example app reads them from AndroidManifest.xml and Info.plist, but any configuration strategy works. See the README and doc/GETTING_STARTED.md.

Platform support #

  • Flutter 3.24+ / Dart 3.5+ — superseded in 2.0.0, which lowered this floor to Flutter 3.19+ / Dart 3.3+ (the current requirement; see README and example/README.md).
  • iOS 15.0+
  • Android API 26+

On Android, the plugin degrades gracefully when the native SDK is absent from the classpath rather than crashing the host app.

0
likes
150
points
38
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin that wraps the native iOS/Android Salesforce Personalization SDKs to deliver personalized UI components (Hero Banner, Recommendations) in Flutter apps.

Repository (GitHub)
View/report issues
Contributing

Topics

#salesforce #personalization #marketing-cloud #flutter-plugin

License

BSD-3-Clause (license)

Dependencies

flutter, url_launcher

More

Packages that depend on flutter_salesforce_personalization

Packages that implement flutter_salesforce_personalization