Skip to content
The mobile SDKs are available on request. Email support@grona.ai to get access.

Basket and context

This page keeps Grona’s view of the cart and the shopper’s situation current. Audience rules, progress bars and substitutes all read the basket, so push a fresh snapshot every time the cart changes. Context covers the shopper’s locale, store and delivery type, and changes less often.

GronaBasket is a snapshot, not a live view of your cart. Build a new one from your cart state and pass it to Grona.setBasket after every change.

import ai.grona.sdk.Grona
import ai.grona.sdk.GronaBasket
fun pushBasket(cart: AcmeCart) {
val basket = GronaBasket.Builder("USD")
.apply {
cart.lines.forEach { line ->
line(line.productId, line.qty, line.unitPriceMinor, line.inStock)
}
}
.totalMinor(cart.totalMinor)
.build()
Grona.setBasket(basket)
}

With milk and spaghetti in the cart, the snapshot is equivalent to:

GronaBasket.Builder("USD")
.line("sku_10432", 2, 349, true) // 2 x 3.49
.line("sku_20877", 1, 199, false) // out of stock
.build() // total 897, the sum of the lines
Builder method What it sets
GronaBasket.Builder(currency) The currency code, for example USD.
line(productId, qty, unitPriceMinor, inStock) One cart line. unitPriceMinor is in minor units, so 349 is 3.49. Set inStock to false for lines that are out of stock.
totalMinor(total) The total that rules such as basket.total compare against. Defaults to the sum of qty * unitPriceMinor over the lines. Set it when your cart total differs from that sum.
GronaBasket.empty(currency) An empty basket. Push it when the cart is cleared, for example after an order.
  1. Push once at launch, as soon as your cart is loaded, so the first screen sees the right basket.

  2. Push after every change: an add, a removal, a quantity change, a stock change, a cleared cart. Hook it into your cart store’s change listener, so changes from Grona actions and from your own screens take the same path.

  3. Build the lines and the total from the same cart state. If your store notifies listeners outside its lock, read the state the listener receives instead of reading the store again, so one snapshot never mixes two states.

setBasket is safe from any thread. Each push replaces the previous snapshot. Live components, such as a free-delivery progress bar, and live decisions re-evaluate against it.

What the basket drives:

  • basket.total, basket.count (units: the sum of quantities), basket.items and basket.out_of_stock in audience rules.
  • The progress component, which is bound live to basket.total.
  • Substitute decisions on cart_line, which show only while the line is in the basket and out of stock.
  • Decisions that exclude products already in the basket.

The basket snapshot stays on the device. Events never carry the full basket. Exception: on keyed placements such as cart_line, campaign events carry the line’s product id as their key, in every arm. So while a cart_line campaign is live, the product ids of the cart lines bound to it leave the device. Grona.reset() clears the snapshot.

GronaContext holds the shopper’s situation. Push it at launch and whenever one of its values changes, for example when the shopper picks another store or switches between delivery and pickup.

import ai.grona.sdk.Grona
import ai.grona.sdk.GronaContext
Grona.setContext(
GronaContext.Builder()
.locale("en")
.branchId("store-0042")
.deliveryType("pickup")
.build(),
)
Builder method Default Rule field
locale(locale) "en" context.locale. Also picks the language of localised campaign text.
branchId(id) null context.branch. Your store or branch id.
deliveryType(type) null context.delivery_type. Your own value, such as delivery or pickup.

The branch and delivery type are your own strings. Use the same values in your campaign rules. Signed-in state is not part of the context: rules read context.signed_in from Grona.identify.

The screen the shopper is on is set per slot and per decision, not here. See Render Grona components.