Skip to main content

Android Reward Currency

A "reward" in the SDK is an amount of some host-defined virtual currency — points, gems, coins, dollars, whatever your app uses. RewardCurrency bundles everything the SDK needs to turn an internal dollar value into a number the user sees:

  • Identity — what the currency is called (currencyName, e.g. "gems") and/or its symbol/code (currencyCode, e.g. "$").
  • Scale & payout — how many currency units a dollar is worth (currencyPerDollar) and what fraction of the gross value the user actually receives (userPayoutPercentage).
  • Display rules — how amounts are laid out (label style, code position, rounding) and on which surfaces the coin icon appears.

The mental model is configure once, read everywhere: assign a single RewardCurrency to ActivationClient.instance.rewardCurrency, and every reward surface (offer wall, receipt summary, boost buttons, scan toast) reads it automatically — no per-screen wiring required.


Configuring it

Build a RewardCurrency with RewardCurrency.default(...) and assign it to the client before launching an SDK surface:

ActivationClient.instance.rewardCurrency = RewardCurrency.default(
currencyName = "gems",
userPayoutPercentage = 0.6, // 60% of gross value reaches the user
currencyPerDollar = 100.0, // $1.00 == 100 gems
)

Re-assigning rewardCurrency takes effect the next time an SDK surface is composed — already-rendered views do not refresh. Assign it during setup (alongside deviceId, hashedEmail, etc.), not mid-flow.

The default value is RewardCurrency.default() with no arguments, so an integrator that never sets it still gets sensible behavior (a "points" currency, coin icon, whole-number amounts).


RewardCurrency.default(...) and normalization

RewardCurrency has no public constructor — and it is not a data class, so there is no copy() or componentN(). You must construct instances through the default(...) factory. This guarantees every value the SDK sees has already been normalized; un-normalized values cannot leak in.

default(...) silently normalizes its inputs:

InputNormalization
currencyNameTrimmed; blank falls back to "points"; truncated to 8 characters.
currencyCodeTrimmed; whitespace-only becomes null.
userPayoutPercentageClamped to 0.4 … 1.0.
rewardCurrencyLabelStyleIf CurrencyCode but currencyCode == null, falls back to CurrencyName (can't render a code that doesn't exist).
rewardCurrencyMessagingTextStyleSame fallback: CurrencyCodeCurrencyName when currencyCode == null.

Defaults

Calling RewardCurrency.default() with no arguments yields:

ParameterDefault
currencyName"points"
currencyCodenull
currencyCodePositionLeading
currencyPerDollar100.0
userPayoutPercentage0.6
rewardCurrencyLabelStyleCurrencyImage
rewardCurrencyMessagingTextStyleCurrencyName
rewardRoundingWhole
currencyImageLocationsRewardCurrencyImageLocation.ALL (all four surfaces)

Properties reference

PropertyTypeMeaning
currencyNameStringDisplay name, max 8 chars (e.g. "gems"). Used by name-based labels and messages.
currencyCodeString?Symbol/code (e.g. "$", "€"). null disables code-based styles.
currencyCodePositionRewardCurrencyCodePositionWhether the code sits before or after the number.
currencyPerDollarDoubleHow many currency units one dollar is worth.
userPayoutPercentageDoubleFraction of gross value paid to the user, 0.4 … 1.0.
rewardCurrencyLabelStyleRewardCurrencyLabelStyleHow amounts are presented on reward labels.
rewardCurrencyMessagingTextStyleRewardCurrencyMessagingTextStyleHow the scan-toast copy phrases the reward.
rewardRoundingRewardCurrencyRoundingWhole-number vs. two-decimal display.
currencyImageLocationsSet<RewardCurrencyImageLocation>Which surfaces show the coin icon.

Display styles

Five enums control exactly how a reward renders.

RewardCurrencyLabelStyle — how reward labels read

ValueOutputNotes
CurrencyImage1,234 (+ coin icon)Bare amount; icon drawn separately (see currencyImageLocations).
CurrencyName1,234 gemsAmount followed by currencyName.
CurrencyCode$1,234 / 1,234€Amount with currencyCode, positioned per currencyCodePosition.

RewardCurrencyCodePosition — where the code sits

Only relevant when a code is shown. Leading$1,234; Trailing1,234€.

RewardCurrencyMessagingTextStyle — scan-toast copy

Controls the "Nice Scan!" toast description:

ValueExample
CurrencyNameYou earned 250 points for this receipt!
CurrencyCodeYou earned $250 for this receipt! (leading) / You earned 250€ for this receipt! (trailing)
NoAmountYou earned a reward for this receipt!

RewardCurrencyRounding — number precision

ValueBehaviorExample
WholeCeils to the next whole unit.1,234
DecimalRounds to two decimal places (always two fraction digits).1,234.56, 1.00

RewardCurrencyImageLocation — per-surface icon visibility

currencyImageLocations is a Set of the surfaces where the coin icon is drawn. Omit a surface to hide the icon there (e.g. show it on offer cards but not the receipt total):

ValueSurface
OfferWallItemOffer cards on the offers wall.
ReceiptTotalRewardThe receipt summary's total-points header.
ReceiptBoostBoost claim buttons.
ReceiptTaskPer-product / task reward rows.

RewardCurrencyImageLocation.ALL is the full set (the default).


Reward amounts: RewardPoint

Reward amounts are typed as RewardPoint, a @JvmInline value class wrapping a Double. It makes "reward points" a distinct type from other numbers (offer payouts, dollar amounts) at zero runtime cost.

@JvmInline
value class RewardPoint(val value: Double) {
fun isZero(): Boolean
fun isEqualTo(other: RewardPoint): Boolean
fun isEqualTo(other: Double): Boolean
companion object { val ZERO: RewardPoint }
}

Read the raw number via .value:

ActivationClient.instance.rewards.collect { reward ->
val points: Double = reward.amount.value
// ...
}

RewardPoint also types the host-supplied base scan reward via ScanReward (null means no scan reward):

ActivationClient.instance.scanReward = ScanReward(reward = RewardPoint(250.0))

Payout math

These conversions are applied internally; they are documented here so you understand how your configured values become the numbers users see.

The base dollar amount is converted into reward currency as:

rewardCurrency = baseAmount × userPayoutPercentage × currencyPerDollar

The raw product is snapped to the nearest cent (stripping floating-point noise), then rewardRounding is applied: Whole ceils to the next integer, Decimal keeps two decimals. Zero, non-finite, or zero-scale inputs short-circuit to 0.

The inverse conversion, used for the base-scan event sent to your webhook, is:

dollars = rewardCurrencyAmount / currencyPerDollar

This inverse intentionally does not apply userPayoutPercentage — the caller supplies an amount already in final reward-currency units.

All number formatting is locale-independent so output is identical regardless of device locale: comma grouping is applied (12345671,234,567) and rounding is carry-safe (0.9991.00 under Decimal).


Reward icon

The coin/reward glyph itself is part of theming, not the reward-currency config. Set it via ActivationTheme.Icons.rewardIcon:

Activation.theme = ActivationTheme(
icons = ActivationTheme.Icons(
rewardIcon = ActivationIcon.Bytes(iconBytes),
),
)

See Android Theme Customization for the full icon API. Per-surface visibility of that icon is controlled here by RewardCurrency.currencyImageLocations.


Where it is applied

RewardCurrency is read at every reward surface:

SurfaceWhat it controls
Offer cardsReward label per rewardCurrencyLabelStyle; icon per OfferWallItem.
Receipt headerAnimated total; icon per ReceiptTotalReward.
Boost buttonsBoost reward label; icon per ReceiptBoost.
Task / product rowsPer-row reward label; icon per ReceiptTask.
Nice Scan toastCopy per rewardCurrencyMessagingTextStyle.

Migrating from the previous reward-currency API

Earlier SDK versions exposed reward configuration as three flat ActivationClient fields. These have been replaced by the single RewardCurrency object. If you have code written against the previous API, update it as follows.

1. Three flat fields → one rewardCurrency

// BEFORE
client.rewardCurrencyName = "gems"
client.rewardPayoutPercentage = 60.0 // 0–100 scale
client.rewardCurrencyPerDollar = 100.0

// AFTER
client.rewardCurrency = RewardCurrency.default(
currencyName = "gems",
userPayoutPercentage = 0.6, // 0–1 normalized
currencyPerDollar = 100.0,
)

2. Payout scale changed from 0–100 to 0–1

rewardPayoutPercentage (a 0–100 percentage) became userPayoutPercentage (a 0–1 fraction) and is now clamped to 0.4 … 1.0. Divide your old value by 100: 60.00.6.

3. Reward amounts are now RewardPoint, not a raw number

Reward event amounts and ScanReward.reward changed to the RewardPoint type. Read the raw number via .value, and wrap when you assign:

// BEFORE
ScanReward(reward = 250)
// AFTER
ScanReward(reward = RewardPoint(250.0))

4. Reward icon moved to theming

The reward icon is no longer a property of the currency config. Set it via ActivationTheme.Icons.rewardIcon (see Reward icon above). Per-surface visibility is now controlled by RewardCurrency.currencyImageLocations.

Migration cheat sheet

BeforeAfter
client.rewardCurrencyName = "gems"currencyName = "gems"
client.rewardPayoutPercentage = 60.0userPayoutPercentage = 0.6 (÷100)
client.rewardCurrencyPerDollar = 100.0currencyPerDollar = 100.0
reward.amount (raw number)reward.amount.value (unwrap RewardPoint)
ScanReward(reward = 250)ScanReward(reward = RewardPoint(250.0))
RewardCurrency.icon = bytesActivationTheme.Icons.rewardIcon = ActivationIcon.Bytes(bytes)