A React Native food-delivery demo app that exercises every public API of the
@salesforce-personalization/react-native-personalization
plugin (v2.0.0). It's built to look and feel like a real consumer app while
giving a reviewer a hands-on way to drive the whole SDK surface and watch each
call in a live activity log.
- React Native 0.81 on the New Architecture (TurboModules + Fabric, Hermes)
- React Navigation v6 — bottom tabs (Home · Browse · Cart · Account) + native stacks
- Consumes the plugin as a real npm customer would — installed from the
public npm registry at the exact production version
2.0.0 - Native SDK init is owned by the host app (no JS-side
configure()); credentials live inInfo.plist(iOS) andAndroidManifest.xml(Android)
Every method on PersonalizationModule is called from the app and mirrored into
the in-app Activity log (Account → View SDK activity log):
| Area | APIs exercised | Where |
|---|---|---|
| Identity — profile | setProfileId, getProfileId |
Account |
| Identity — attributes | setAttribute, setAttributes, getAttributes, clearAttribute, clearAllAttributes |
Account |
| Identity — party | setPartyIdentificationName/Number/Type, getPartyIdentificationName/Number/Type |
Account |
| Consent | setConsent, isConsentOptIn, getDeviceId |
Account |
| Preview | handlePreviewUrl, isPreview |
Account + deep links |
| Logging | setLogging |
Account + app bootstrap |
| Events | track() for CustomEvent, EngagementEvent, SystemEvent, CartEvent (add/remove/replace), OrderEvent, CatalogEvent |
across all screens |
| Content zones | <ContentZone>, <MockContentZone>, useContentZoneController, OOTB SalesforceBanner() / SalesforceRecommendations(), custom components |
Home / Browse / RestaurantDetail / DishDetail / Cart |
All emitted event names are prefixed with the app name (e.g.
forkly_banner_tap, forkly_app_launched) so they're easy to spot in
downstream analytics. CartEvent / OrderEvent / CatalogEvent use the SDK's
fixed subtype enums; Custom / Engagement / System events take free-form names.
The app renders these ContentZone names. Each is decorated with a LIVE /
MOCK tag and lists its allowed component names.
| Zone name | Type | Allowed component(s) | Screen |
|---|---|---|---|
home_hero_banner |
live | SFPBanner |
Home |
home_chef_spotlight |
live | custom DishSpotlight |
Home |
home_recommendations |
live | SFPRecommendations |
Home |
home_mock_promo |
mock | SFPBanner |
Home |
browse_category_banner |
live | SFPBanner |
Browse |
restaurant_recommendations |
live | SFPRecommendations |
RestaurantDetail |
dish_pairings |
live | SFPRecommendations |
DishDetail |
cart_addons |
live | SFPRecommendations |
Cart |
To see live personalized content you must opt consent in (Account → Consent → Opt in) and have matching campaigns configured for these zone names in your Personalization org. Without a live campaign, zones show a graceful fallback (e.g. "Cdp consent not opted-in" until you opt in);
home_mock_promoalways renders offline viaMockContentZone.
src/
personalization/
sdk.ts Thin, logged wrapper over the entire PersonalizationModule surface
zones.ts OOTB SalesforceBanner/SalesforceRecommendations factories + custom DishSpotlight + mock content
eventLog.ts In-memory activity log feeding the Activity screen
components/
LabeledContentZone.tsx Demo chrome around <ContentZone>/<MockContentZone>
custom/DishSpotlight.tsx Custom component rendered inside a live zone
catalog.tsx, ui.tsx Presentational building blocks
context/
PersonalizationContext.tsx App bootstrap: setLogging, launch event, consent/profile sync,
preview deep-link listener
CartContext.tsx Cart state + CartEvent tracking
screens/ Home · Browse · RestaurantDetail · DishDetail · Cart · Account · Activity
navigation/ Bottom tabs + native stacks + deep-link linking config
data/ Static restaurant/dish catalog
theme/ Colors, spacing, typography
Screens import from src/personalization/sdk.ts rather than touching the module
directly — one place demonstrates the full public surface and logs every call.
Complete the RN Set Up Your Environment guide first. This app uses the New Architecture, which is the RN 0.81 default.
npm installThe app pins
@salesforce-personalization/react-native-personalization to 2.0.0 from the
public npm registry. Local native-SDK testing is optional and documented in
LOCAL_SDK_TESTING.md.
bundle install # first time only — installs CocoaPods
bundle exec pod install # from ios/, or: (cd ios && bundle exec pod install)
npm run iosnpm run androidThe host app owns native SDK initialization. Personalization credentials are read from platform config at launch (replace with your own; do not commit real values):
- iOS —
ios/Forkly/Info.plistkeys undercom.salesforce.personalization.*(CDP_APP_ID,CDP_ENDPOINT,DATASPACE,CDN_URL) - Android —
android/app/src/main/AndroidManifest.xml<meta-data>entries undercom.salesforce.personalization.*
Personalization preview links use the sfp-preview query token. Forkly registers
two URL schemes on both platforms — personalizationdemo:// (the scheme used
by Personalization preview QR codes) and forkly:// — and forwards any
incoming URL containing sfp-preview to PersonalizationModule.handlePreviewUrl
automatically (cold start + warm), via the listener in PersonalizationContext.
You can also trigger it by hand: Account → Preview, paste a preview URL, tap Load preview. Test the deep link from a terminal:
# iOS Simulator
xcrun simctl openurl booted "personalizationdemo://preview?sfp-preview=<token>"
# Android emulator
adb shell am start -a android.intent.action.VIEW \
-d "personalizationdemo://preview?sfp-preview=<token>" com.forklyUse isPreview("<zone_name>") (Account → Preview) to check whether a given zone
is currently in preview mode.
If you already have a Metro instance on 8081 (e.g. another RN project), run
Forkly's Metro on a different port and point the app at it:
npm start -- --port 8082- Android — map the device's
8081to your Metro port:adb reverse tcp:8081 tcp:8082 - iOS — launch with
RCT_jsLocation=localhost:8082(or set the bundler port in the in-app Dev Menu → Configure Bundler).
Two changes were needed to build the plugin on Android under RN 0.81 + New Arch. Both look like plugin-side packaging gaps worth upstreaming:
- New Arch codegen isn't wired up. The plugin ships a
codegenConfigand TurboModule specs but itsandroid/build.gradlenever appliescom.facebook.react, so CMake fails with "add_subdirectory given source .../codegen/jni which is not an existing directory". Fixed locally by applying the plugin whennewArchEnabled(as react-native-screens / safe-area-context do). - Kotlin metadata version mismatch. The SDK AAR is compiled with a newer
Kotlin stdlib than RN 0.81's pinned Kotlin
2.1.20K2 frontend can read, causing a type-checker crash. Fixed locally by bumpingkotlinVersioninandroid/build.gradle.
See the respective build.gradle files for the exact diffs and inline rationale.