> For the complete documentation index, see [llms.txt](https://docs.firework.com/firework-for-developers/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.firework.com/firework-for-developers/android-sdk/integration-guide/shoppable-videos/product-cards.md).

# Product Cards

Product cards display a list of available products in the video. Each card represents a product with its image, title, price, and action button.

This page covers how to customize product card appearance and behavior:

* [Product Card Appearance (V2)](#product-card-appearance-v2) - Customize colors, corner radius, and borders of V2 product cards
* [Product Indicator Customization (V2)](#product-indicator-customization-v2) - Customize the carousel indicator
* [Product Card Click Handler](#product-card-click-handler) - Control what happens when a card is tapped
* [Version 1 Product Cards Configuration](#version-1-product-cards-configuration) - Legacy V1 card options
* [Custom Product Cards (V1 only)](#custom-product-cards-v1-only) - Provide a fully custom card view on V1 players

> **Note**: For how to enable Player Version 2 (V2) and the differences between V1 and V2 product cards, see [Shoppable Videos](/firework-for-developers/android-sdk/integration-guide/shoppable-videos.md#new-product-cards).

## Product Card Appearance (V2)

V2 product cards support appearance customization through `ShoppingViewOptions.productCardV2Configuration`. You can customize the card background color, corner radius, border color, and the text colors of the product name and price labels.

The configuration applies to V2 product cards in the short video player (including trailers), the livestream player, and livestream replays.

```kotlin
import android.graphics.Color
import com.firework.shopping.ProductCardV2BorderConfiguration
import com.firework.shopping.ProductCardV2Configuration
import com.firework.shopping.ProductCardV2LabelConfiguration
import com.firework.shopping.ProductCardV2PriceConfiguration
import com.firework.shopping.ShoppingViewOptions

FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardV2Configuration = ProductCardV2Configuration(
            backgroundColor = Color.parseColor("#FFFFFF"),
            cornerRadius = resources.getDimension(R.dimen.product_card_corner_radius),
            borderConfiguration = ProductCardV2BorderConfiguration(
                color = Color.parseColor("#B3FFFFFF"),
            ),
            nameLabel = ProductCardV2LabelConfiguration(
                textColor = Color.parseColor("#212121"),
            ),
            priceConfiguration = ProductCardV2PriceConfiguration(
                priceLabel = ProductCardV2LabelConfiguration(
                    textColor = Color.parseColor("#212121"),
                ),
                discountLabel = ProductCardV2LabelConfiguration(
                    textColor = Color.parseColor("#D32F2F"),
                ),
                originalPriceLabel = ProductCardV2LabelConfiguration(
                    textColor = Color.parseColor("#757575"),
                ),
            ),
        ),
    ),
)
```

### Configuration Properties

All properties are optional. A `null` value keeps the SDK default appearance for that element.

| Property                                          | Type         | Default when `null`                                   |
| ------------------------------------------------- | ------------ | ----------------------------------------------------- |
| `backgroundColor`                                 | `@ColorInt`  | Default gradient background                           |
| `cornerRadius`                                    | `Float` (px) | 8dp card corners, 10% product image corners           |
| `borderConfiguration.color`                       | `@ColorInt`  | White with 70% opacity (border width is fixed to 1dp) |
| `nameLabel.textColor`                             | `@ColorInt`  | SDK default                                           |
| `priceConfiguration.priceLabel.textColor`         | `@ColorInt`  | SDK default                                           |
| `priceConfiguration.discountLabel.textColor`      | `@ColorInt`  | SDK default                                           |
| `priceConfiguration.originalPriceLabel.textColor` | `@ColorInt`  | SDK default                                           |

### Important Notes

* All color values are `@ColorInt` values. Use `Color.parseColor(...)`, `ContextCompat.getColor(...)`, or similar.
* `cornerRadius` is a pixel value and is applied to both the card and the product image. Use `resources.getDimension()` when defining the value in `dp`.
* The configuration only affects the built-in V2 card layout. To provide a fully custom card view, see [Custom Product Cards (V1 only)](#custom-product-cards-v1-only) — note that this is only supported on V1 players.

***

## Product Indicator Customization (V2)

V2 product cards can show a product indicator for the product carousel when enough products are available. You can customize the indicator size globally through `ShoppingViewOptions.productCardIndicatorConfiguration`.

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardIndicatorConfiguration = ProductCardIndicatorConfiguration(
            widthPx = resources.getDimensionPixelSize(R.dimen.product_indicator_width),
            heightPx = resources.getDimensionPixelSize(R.dimen.product_indicator_selected_height),
            unselectedHeightPx = resources.getDimensionPixelSize(R.dimen.product_indicator_unselected_height),
        ),
    ),
)
```

Example dimension resources:

```xml
<dimen name="product_indicator_width">30dp</dimen>
<dimen name="product_indicator_selected_height">3dp</dimen>
<dimen name="product_indicator_unselected_height">1dp</dimen>
```

### **Default Values**

If you do not provide a `ProductCardIndicatorConfiguration`, the SDK uses the default indicator dimensions:

| Property             | Default value | Description                                 |
| -------------------- | ------------- | ------------------------------------------- |
| `widthPx`            | `30dp`        | Maximum width of each indicator             |
| `heightPx`           | `3dp`         | Visible height of the selected indicator    |
| `unselectedHeightPx` | `1dp`         | Visible height of each unselected indicator |

If you customize any indicator size but leave `unselectedHeightPx` unset, the SDK derives the unselected height from the selected indicator height using `max(1px, selectedIndicatorHeight / 3)`.

### **Size Limits**

All values are pixel values and must be greater than `0`. Use `resources.getDimensionPixelSize()` when defining the size in `dp`.

`widthPx` is the desired maximum width, not a guaranteed final width. If the indicators do not fit in the available player width, the SDK automatically reduces each indicator width while keeping a minimum visible width.

`heightPx` and `unselectedHeightPx` are not capped by the SDK. Very large height values can make the indicator container taller and may affect the product card layout, so use values close to the defaults unless your design requires otherwise.

The indicator is shown when there is more than one product in portrait orientation, or more than two products in landscape orientation.

***

## Product Card Click Handler

When a user taps a product card, the SDK determines the navigation behavior using a three-level priority system. This gives you full flexibility: from zero-configuration defaults to complete custom control.

### Priority Order

```mermaid
flowchart TD
    Click["User taps product card"] --> CheckListener{"OnProductCardClickListener set?"}
    CheckListener -->|"Yes"| CallListener["Call listener.onProductCardClick()"]
    CallListener --> ListenerResult{"Returns true?"}
    ListenerResult -->|"true"| HostHandles["Host app handles navigation"]
    ListenerResult -->|"false"| CheckBehavior
    CheckListener -->|"No"| CheckBehavior{"ProductCardClickBehavior type?"}
    CheckBehavior -->|"OpenExternalLink"| ValidateURL{"Product URL available?"}
    ValidateURL -->|"Yes"| OpenExternal["SDK opens external URL via ACTION_VIEW"]
    ValidateURL -->|"No"| FallbackPDP["Report error"]
    CheckBehavior -->|"OpenProductDetails or null"| OpenPDP["SDK opens built-in PDP"]
    OpenExternal --> CheckPip{"enterPip = true?"}
    CheckPip -->|"Yes"| EnterPip["SDK enters PiP mode"]
    CheckPip -->|"No"| Done["Done"]
```

| Priority    | Mechanism                                     | When to use                                                                               |
| ----------- | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| 1 (Highest) | `setOnProductCardClickListener`               | Full custom control: open your own activity, track analytics, or perform any custom logic |
| 2           | `ProductCardClickBehavior.OpenExternalLink`   | Zero-configuration external navigation: SDK opens the product URL automatically           |
| 3 (Default) | `ProductCardClickBehavior.OpenProductDetails` | SDK opens the built-in product details page (PDP)                                         |

> **Backward compatible**: If you do not configure anything, the SDK opens the built-in PDP (same as previous versions).

***

### Path 1: Custom Listener (Highest Priority)

Use `setOnProductCardClickListener` when you need full control over what happens when a product card is clicked.

```kotlin
FireworkSdk.shopping.setOnProductCardClickListener { productId, unitId, productWebUrl, videoInfo, product ->
    // Perform custom navigation
    openProductPage(productWebUrl)

    // Optionally enter Picture-in-Picture mode
    FireworkSdk.enterPip()

    // Return true: your app handled navigation, SDK does nothing further
    // Return false: SDK falls through to ProductCardClickBehavior (Path 2/3)
    true
}
```

**Return value matters:**

* `true` — Your app handled navigation. The SDK takes no further action.
* `false` — The SDK falls through to the `ProductCardClickBehavior` setting (Path 2 or Path 3).

To remove the listener:

```kotlin
FireworkSdk.shopping.setOnProductCardClickListener(null)
```

***

### Path 2: ViewOptions-Driven External Link (Zero Configuration)

Use `ProductCardClickBehavior.OpenExternalLink` when you want the SDK to automatically open the product's external URL without writing any listener code.

```kotlin
val viewOptions = ViewOptions.Builder()
    .playerOption(
        PlayerOption.Builder()
            .productCardClickBehavior(
                ProductCardClickBehavior.OpenExternalLink()
            )
            .build()
    )
    .build()

videoFeedView.init(viewOptions)
```

The SDK will:

1. Extract the product URL from `product.units.firstOrNull()?.url`
2. Open it via `Intent.ACTION_VIEW`
3. If the URL is missing, report an error

**With custom Intent flags:**

```kotlin
ProductCardClickBehavior.OpenExternalLink(
    launchFlags = Intent.FLAG_ACTIVITY_SINGLE_TOP
)
```

**With automatic PiP mode:**

When `enterPip = true`, the SDK automatically enters Picture-in-Picture mode after opening the external link, allowing the video to continue playing in a small overlay while the user browses the product page.

```kotlin
ProductCardClickBehavior.OpenExternalLink(
    enterPip = true
)
```

***

### Path 3: Built-in Product Details Page (Default)

If no listener is set and `productCardClickBehavior` is `OpenProductDetails` (or not configured), the SDK opens its built-in product details page.

```kotlin
// Explicit (equivalent to not setting it at all)
PlayerOption.Builder()
    .productCardClickBehavior(ProductCardClickBehavior.OpenProductDetails)
    .build()
```

***

### Combining Listener and ViewOptions

You can set both a listener and a `ProductCardClickBehavior`. The listener always gets first priority:

```kotlin
// Configure ViewOptions as fallback
val viewOptions = ViewOptions.Builder()
    .playerOption(
        PlayerOption.Builder()
            .productCardClickBehavior(
                ProductCardClickBehavior.OpenExternalLink(enterPip = true)
            )
            .build()
    )
    .build()

videoFeedView.init(viewOptions)

// Listener takes priority when set
FireworkSdk.shopping.setOnProductCardClickListener { productId, unitId, productWebUrl, videoInfo, product ->
    if (shouldHandleInApp(product)) {
        navigateToProductPage(productId)
        true  // Handled by app
    } else {
        false // Fall through to OpenExternalLink behavior
    }
}
```

In this example:

* If the listener returns `true`, the app handles navigation.
* If the listener returns `false`, the SDK opens the external link and enters PiP mode (as configured in ViewOptions).

## Version 1 Product Cards Configuration

> **Note**: These options apply to the V1 (classic) product cards. For V2 card appearance, use [`ProductCardV2Configuration`](#product-card-appearance-v2).

### Configurable Properties

* **Corner Radius**: Rounded corners of product cards
* **CTA Button Label**: "Shop now" or "Buy now"
* **CTA Button Visibility**: Show or hide the action button
* **Click Action**: Custom handler for card clicks

### Corner Radius

The default corner radius is `4dp`. You can customize it:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardsOptions = ProductCardsOptions.Default(
            cornerRadius = resources.getDimensionPixelSize(R.dimen.corner_radius)
        ),
    ),
)
```

**Visual Comparison:**

<table><thead><tr><th width="384" align="center">24dp radius</th><th align="center">4dp radius</th></tr></thead><tbody><tr><td align="center"><img src="/files/BqdySNvtbqkXElCqvEV5" alt=""></td><td align="center"><img src="/files/dr3xaFtZdmnAit96BrHd" alt=""></td></tr></tbody></table>

### CTA Button Label

The default label is "Shop now". You can change it to "Buy now":

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardsOptions = ProductCardsOptions.Default(
            ctaButtonText = ProductCardsOptions.Text.BUY_NOW
            // or ProductCardsOptions.Text.SHOP_NOW
        ),
    ),
)
```

<figure><img src="/files/PkHwSmGA9ooaTRRZamkA" alt="" width="303"><figcaption><p>Product card CTA button</p></figcaption></figure>

### CTA Button Visibility

Control whether the CTA button is shown on product cards:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        productCardsOptions = ProductCardsOptions.Default(
            isCtaVisible = true  // or false
        ),
    ),
)
```

**Visual Comparison:**

|        "Shop now" visible        |        "Shop now" hidden:        |
| :------------------------------: | :------------------------------: |
| ![](/files/ZlkjpTNuiKb3kkh2CtCW) | ![](/files/CCMRl9hFC0RQCqJwmmiT) |

## Custom Product Cards (V1 only)

> **Important**: Custom product cards require enablement by your Firework account manager.

> **Important**: Custom product cards are only supported by the **V1 (classic) players**. When V2 players are enabled, `ProductCardsOptions.Custom` is ignored and the SDK renders the built-in V2 card. To customize the V2 card appearance, use [`ProductCardV2Configuration`](#product-card-appearance-v2) instead.

The SDK allows you to provide a fully custom view for product cards:

```kotlin
FireworkSdk.shopping.setShoppingViewOptions(
    ShoppingViewOptions(
        theme = ShoppingTheme.LIGHT,
        productCardsOptions = ProductCardsOptions.Custom(
            widthPx = width,
            heightPx = height,
            spaceBetweenItems = spaceBetweenItems,
            cardViewStartEndPadding = padding,
            customViewProvider = { MyCustomProductCard(this) },
        ),
    ),
)
```

### Implementing Custom Product Card View

Create a custom view by extending `FwProductCardView`:

```kotlin
class MyCustomProductCard @JvmOverloads constructor(
    context: Context, 
    attrs: AttributeSet? = null, 
    defStyle: Int = 0,
) : FwProductCardView(context, attrs, defStyle) {
    
    private val title: TextView
    private val price: TextView
    private val image: ImageView
    
    init {
        inflate(context, R.layout.custom_product_card_item, this)
        title = findViewById(R.id.title)
        price = findViewById(R.id.price)
        image = findViewById(R.id.image)
    }

    override fun bind(product: ProductCardDetails, videoInfo: VideoInfo) {
        title.text = product.productTitle
        price.text = "${product.price?.amount ?: 0.0} ${product.price?.currencyCode}"
        
        Glide.with(image.context)
            .load(product.productImageUrl)
            .placeholder(R.drawable.ic_product_placeholder)
            .error(R.drawable.ic_product_placeholder)
            .into(image)
    }
}
```

### Important Notes

* **Size Requirements**: Width and height must be in pixels. The SDK may limit card size to respect the [safe zone specifications](https://help.firework.com/safe-zone-specifications-for-firework-videos).
* **Click Handling**: Custom product cards cannot have clickable elements. All clicks are intercepted by the SDK.

## Related Documentation

* [Shoppable Videos](/firework-for-developers/android-sdk/integration-guide/shoppable-videos.md) - Overview and enabling Player Version 2
* [Cart & Checkout](/firework-for-developers/android-sdk/integration-guide/shoppable-videos/cart-and-checkout.md) - Shopping cart, CTA button, and PDP configuration
* [Product Hydration](/firework-for-developers/android-sdk/integration-guide/shoppable-videos/product-hydration.md) - Real-time product data integration
