Flutter Ecommerce Activity Tracking

Ecommerce Activity Tracking

Reteno ecommerce events describe the customer journey through a product catalog, cart, and order flow. Send every supported event through the instance API:

await Reteno().logEcommerceEvent(event);

The SDK timestamps the event itself; only an order payload carries its own date. The returned Future completes after the event is dispatched to the native SDK, not after the backend ingests it.

The Dart models use double, but iOS narrows every monetary value to a 32-bit Float while Android keeps Double. Use ordinary currency-scale values.

Supported Events

Event classUse
RetenoEcommerceProductViewedProduct card viewed
RetenoEcommerceProductCategoryViewedProduct-category/listing page viewed
RetenoEcommerceProductAddedToWishlistProduct added to a wishlist
RetenoEcommerceCartUpdatedCart contents changed
RetenoEcommerceOrderCreatedOrder created
RetenoEcommerceOrderUpdatedExisting order updated
RetenoEcommerceOrderDeliveredOrder marked delivered
RetenoEcommerceOrderCancelledOrder marked cancelled
RetenoEcommerceSearchRequestCatalog search performed

Product Viewed

final product = RetenoEcommerceProduct(
  productId: 'PRODUCT_ID',
  price: 20.0,
  inStock: true,
  attributes: {
    'size': ['23', '42'],
    'color': ['white', 'orange'],
  },
);

await Reteno().logEcommerceEvent(
  RetenoEcommerceProductViewed(
    product: product,
    currency: 'UAH',
  ),
);

product is required; currency is optional and falls back to the organization default.

RetenoEcommerceProduct

PropertyTypeDescription
productIdStringUnique identifier of the product in your catalog.
pricedoubleCurrent price including taxes and discounts.
inStockbooltrue if the product is available for purchase.
attributesMap<String, List<String>>?Required constructor argument; pass null or an attribute map such as { 'size': ['M'] }.

Product Category Viewed

final category = RetenoEcommerceCategory(
  productCategoryId: 'CATEGORY_ID',
  attributes: {
    'gender': ['kids'],
  },
);

await Reteno().logEcommerceEvent(
  RetenoEcommerceProductCategoryViewed(category: category),
);
PropertyTypeDescription
productCategoryIdStringCategory identifier (slug, code, or UUID).
attributesMap<String, List<String>>?Optional category metadata.

Product Added to Wishlist

await Reteno().logEcommerceEvent(
  RetenoEcommerceProductAddedToWishlist(
    product: product,
    currency: 'EUR',
  ),
);

This event uses the same RetenoEcommerceProduct model as Product Viewed.

Cart Updated

final cartProducts = [
  const RetenoEcommerceProductInCart(
    productId: 'uut',
    price: 20.0,
    quantity: 1,
  ),
  const RetenoEcommerceProductInCart(
    productId: 'lk',
    price: 100.0,
    quantity: 3,
    discount: 10.0,
    name: 'Example product',
    category: 'CATEGORY_ID',
    attributes: {
      'color': ['orange'],
    },
  ),
];

await Reteno().logEcommerceEvent(
  RetenoEcommerceCartUpdated(
    cartId: 'CART_ID',
    products: cartProducts,
    currency: 'UAH',
  ),
);

cartId and products are required; currency is optional.

RetenoEcommerceProductInCart

PropertyTypeDescription
productIdStringProduct identifier.
pricedoubleUnit price before discount.
quantityintNumber of units; keep it within the signed 32-bit range.
discountdouble?Discount per unit (absolute, optional).
nameString?Product name (optional).
categoryString?Product category name or ID (optional).
attributesMap<String, List<String>>?Extra attributes (optional).

iOS forwards only productId, price, and quantity; the optional fields are dropped. Android forwards all of them.

Order Created or Updated

RetenoEcommerceOrderCreated and RetenoEcommerceOrderUpdated use the same payload. For a cross-platform date, send UTC ISO 8601 without fractional seconds:

final orderDate = DateTime.now()
    .toUtc()
    .toIso8601String()
    .replaceFirst(RegExp(r'\.\d+Z$'), 'Z');

final order = RetenoEcommerceOrder(
  externalOrderId: 'ORDER_ID',
  totalCost: 300.0,
  status: RetenoEcommerceOrderStatus.initialized,
  date: orderDate,
  cartId: 'CART_ID',
);

await Reteno().logEcommerceEvent(
  RetenoEcommerceOrderCreated(
    order: order,
    currency: 'UAH',
  ),
);

order is required; currency is optional.

RetenoEcommerceOrder

PropertyTypeDescription
externalOrderIdStringOrder ID from your commerce backend.
totalCostdoubleGrand total charged to the customer.
statusRetenoEcommerceOrderStatusinitialized, inProgress, delivered, or cancelled, sent as INITIALIZED, IN_PROGRESS, DELIVERED, and CANCELLED.
dateString (ISO 8601)Date and time the order was placed.
cartIdString?Cart that generated the order (optional).
email, phoneString?Customer contacts (optional).
firstName, lastNameString?Customer name (optional).
shipping, discount, taxesdouble?Order price components (optional).
restoreUrl, statusDescriptionString?Restore link and status details (optional).
storeId, sourceString?Store and source identifiers (optional).
deliveryMethod, paymentMethod, deliveryAddressString?Delivery and payment details (optional).
itemsList<RetenoEcommerceItem>?Order line items (optional).
attributesMap<String, List<String>>?Additional order attributes (optional).

The model has no externalCustomerId property.

RetenoEcommerceItem requires externalItemId, name, category, url (String), and quantity, cost (double). It also accepts optional imageUrl and description.

iOS substitutes the current native time when date contains fractional seconds; Android also accepts offset and local ISO 8601 input. The format in the example above works on both platforms.

Order attributes are encoded differently per platform: Android joins each list into one comma-separated string, iOS sends {"values": [...]}. Do not use them for cross-platform segmentation.

Order Delivered or Cancelled

Both events take the existing external order ID:

await Reteno().logEcommerceEvent(
  RetenoEcommerceOrderDelivered(externalOrderId: 'ORDER_ID'),
);

await Reteno().logEcommerceEvent(
  RetenoEcommerceOrderCancelled(externalOrderId: 'ORDER_ID'),
);

Search Request

query is required and isFound is nullable. Pass an explicit boolean: Android converts null to false, iOS preserves null.

await Reteno().logEcommerceEvent(
  RetenoEcommerceSearchRequest(
    query: 'iphone',
    isFound: true,
  ),
);

Custom Events

If the use case does not match a predefined ecommerce event, use the generic event API:

await Reteno().logEvent(
  event: RetenoCustomEvent(
    eventTypeKey: 'custom_event_type',
    dateOccurred: DateTime.now(),
    parameters: [
      RetenoCustomEventParameter('parameter_name', 'value'),
    ],
  ),
);

See Tracking User Behaviour for custom-event semantics and the iOS timestamp limitation.

Currency Codes

Use the supported ISO 4217 codes USD, EUR, or UAH. If currency is null, the organization's default currency is used.