Shopping
Shopping Integration
Stories created in JOIN Studio can include shopping interactions (CTAs) such as Add to Cart, See Cart, and Shopping Redirect. The Flutter plugin wires these listener callbacks for you on both iOS and Android. You only need to handle the callbacks in your app to perform the desired actions.
Events and callback signatures
-
Add to Cart
- Signature (widgets): onAddToCart(String offerId)
- Signature (standalone): onAddToCart(String offerId)
- Payload: offerId (may be an empty string when not provided by content)
- Typical use: add the item to your app cart, update UI, optionally dismiss the player
-
See Cart
- Signature (widgets): onSeeToCart()
- Signature (standalone): onSeeToCart()
- Payload: none
- Typical use: navigate to your app’s cart screen, optionally dismiss the player first
-
Shopping Redirect
- Signature (widgets): onShoppingRedirect(String offerId)
- Signature (standalone): onShoppingRedirect(String offerId)
- Payload: offerId (may be an empty string when not provided)
- Typical use: open a PDP or deep link inside your app; if external, use your web handler
Notes
- Delivery: callbacks are invoked on the UI thread via the plugin event channel.
- Acknowledgment: native SDKs may simulate async completion internally; you don’t need to return a result from Flutter. Implement your own UX and state updates as needed.
- Robustness: treat missing or empty offerId gracefully
- Analytics: if enabled, you can also observe onAnalyticsEvent(eventName, payload) for additional tracking.
Usage with widgets (BubbleWidget / CardWidget)
Attach callbacks directly to each widget. Example:
BubbleWidget(
alias: 'homepage-stories',
onAddToCart: (offerId) async {
// 1) Update your cart state
await cart.add(offerId);
// 2) Optional UX
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Added to cart')),
);
// 3) Optional: close player if it is showing
// await JOINStories.dismissPlayer();
},
onSeeToCart: () async {
// Optional: close player, then navigate
await JOINStories.dismissPlayer();
Navigator.of(context).pushNamed('/cart');
},
onShoppingRedirect: (offerId) async {
// Route to PDP or handle deep-link based on offerId
Navigator.of(context).pushNamed('/product/$offerId');
},
);
The same callbacks exist on CardWidget (list or grid):
CardWidget(
alias: 'recommendations',
isGrid: true,
onAddToCart: (offerId) => cart.add(offerId),
onSeeToCart: () {
JOINStories.dismissPlayer();
Navigator.of(context).pushNamed('/cart');
},
onShoppingRedirect: (offerId) {
Navigator.of(context).pushNamed('/product/$offerId');
},
);
Usage with the Standalone Player
Pass the shopping callbacks when launching the player:
await JOINStories.startPlayer(
'homepage-stories',
onAddToCart: (offerId) async {
await cart.add(offerId);
},
onSeeToCart: () async {
await JOINStories.dismissPlayer();
Navigator.of(context).pushNamed('/cart');
},
onShoppingRedirect: (offerId) {
Navigator.of(context).pushNamed('/product/$offerId');
},
// Optionally also listen to analytics
onAnalyticsEvent: (eventName, payload) {
analytics.log('join_$eventName', payload);
},
);
Best practices
-
Keep UI responsive: do not block the main thread; show lightweight toasts/snackbars and offload heavy work to async code.
-
Dismiss before navigation: if a story player is visible, call JOINStories.dismissPlayer() before pushing a new route to avoid overlapping UIs.
-
Error handling: if cart updates fail, communicate failure to the user and allow retry.
-
Testing: verify that each CTA set in JOIN Studio triggers the expected callback and that empty or unknown offerId values are handled safely.
Updated 7 days ago