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 or onResult(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
  • See Cart

    • Signature: seeToCart()
    • Payload: none
    • Typical use: navigate to your app’s cart screen, optionally dismiss the player first
  • 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

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.