Shopping
Shopping Integration
Stories created in JOIN Studio can include shopping CTAs such as Add to Cart, See Cart, and Shopping Redirect. On Android, these are delivered through JOINStoriesListener
. You only need to implement the callbacks in your app to perform the desired actions.
Events and callback signatures
-
Add to Cart
- Signature:
addToCart(offerId: String, onResult: (Boolean) -> Unit)
- Payload:
offerId
(may be an empty string when not provided by content) - Completion: call
onResult(true)
on success oronResult(false)
on failure so the SDK can update its UI (e.g., stop loader, show confirmation) - Typical use: add the item to your app cart, update UI, optionally dismiss the player
- Signature:
-
See Cart
- Signature:
seeToCart()
- Payload: none
- Typical use: navigate to your app’s cart screen, optionally dismiss the player first
- Signature:
-
Shopping Redirect
- Signature:
shoppingRedirect(offerId: String)
- Payload:
offerId
(may be an empty string when not provided) - Typical use: open a PDP or handle a deep link inside your app; if external, use your web handler
- Signature:
Notes
- Delivery: callbacks are invoked on the main thread.
- Acknowledgment: for Add to Cart, you must invoke
onResult(Boolean)
to acknowledge completion; you do not return a value from the method itself. - Robustness: handle missing or empty
offerId
gracefully. - Analytics: if needed, observe
JOINStoriesAnalyticsListener.joinStoriesAnalyticsCallback(event, payload)
for additional tracking.
Usage with widgets (BubbleTriggerView / CardTriggerView)
Attach a JOINStoriesListener
when displaying your trigger widget. Example with BubbleTriggerView
:
val bubble = BubbleTriggerView(context).apply {
// Configure and mount your widget (alias, options, etc.)
// e.g., setAlias("homepage-stories") if your API exposes it, or pass via your mounting method
}
val listener = object : JOINStoriesListener {
override fun addToCart(offerId: String, onResult: (Boolean) -> Unit) {
// 1) Update your cart state (async if needed)
cartRepository.add(offerId) { success ->
// 2) Inform the SDK of the outcome (required)
onResult(success)
// 3) Optional UX
if (success) {
Toast.makeText(context, "Added to cart", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Failed to add to cart", Toast.LENGTH_SHORT).show()
}
}
}
override fun seeToCart() {
// Optional: close the player first if visible
JOINStories.dismissPlayer()
// Navigate to your cart screen
navController.navigate(R.id.cart)
}
override fun shoppingRedirect(offerId: String) {
// Route to PDP or deep link based on offerId
navController.navigate("app://product/$offerId")
}
}
// Mount the widget and pass the listener where your API accepts it
// e.g., container.addView(bubble) and/or JOINStories.mountTrigger(bubble, alias, configuration, listener)
The same callbacks apply when using CardTriggerView
(list or grid). Example:
val cardGrid = CardTriggerView(context).apply {
// Configure as grid/list and set alias (e.g., "recommendations")
}
val listener = object : JOINStoriesListener {
override fun addToCart(offerId: String, onResult: (Boolean) -> Unit) {
cartRepository.add(offerId) { onResult(it) }
}
override fun seeToCart() {
JOINStories.dismissPlayer()
navController.navigate(R.id.cart)
}
override fun shoppingRedirect(offerId: String) {
navController.navigate("app://product/$offerId")
}
}
// Mount the card widget and pass the listener with your integration method
Usage with the Standalone Player
Pass the shopping callbacks when launching the player:
JOINStories.startPlayer(
context = requireContext(),
alias = "homepage-stories",
listener = object : JOINStoriesListener {
override fun addToCart(offerId: String, onResult: (Boolean) -> Unit) {
cartRepository.add(offerId) { success ->
onResult(success) // required to update SDK UI
}
}
override fun seeToCart() {
JOINStories.dismissPlayer()
navController.navigate(R.id.cart)
}
override fun shoppingRedirect(offerId: String) {
navController.navigate("app://product/$offerId")
}
},
analyticsListener = object : JOINStoriesAnalyticsListener {
override fun joinStoriesAnalyticsCallback(
event: JOINTrackingEvent,
payload: Map<String, Any?>
) {
analytics.log("join_${event.name}", 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 starting a new Activity/Fragment to avoid overlapping UIs. - Error handling: if cart updates fail, communicate failure to the user and allow retry; always call
onResult(false)
on failure. - Testing: verify that each CTA set in JOIN Studio triggers the expected callback and that empty or unknown
offerId
values are handled safely.
Updated 6 days ago