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:
| Input | Normalization |
|---|---|
currencyName | Trimmed; blank falls back to "points"; truncated to 8 characters. |
currencyCode | Trimmed; whitespace-only becomes null. |
userPayoutPercentage | Clamped to 0.4 … 1.0. |
rewardCurrencyLabelStyle | If CurrencyCode but currencyCode == null, falls back to CurrencyName (can't render a code that doesn't exist). |
rewardCurrencyMessagingTextStyle | Same fallback: CurrencyCode → CurrencyName when currencyCode == null. |
Defaults
Calling RewardCurrency.default() with no arguments yields:
| Parameter | Default |
|---|---|
currencyName | "points" |
currencyCode | null |
currencyCodePosition | Leading |
currencyPerDollar | 100.0 |
userPayoutPercentage | 0.6 |
rewardCurrencyLabelStyle | CurrencyImage |
rewardCurrencyMessagingTextStyle | CurrencyName |
rewardRounding | Whole |
currencyImageLocations | RewardCurrencyImageLocation.ALL (all four surfaces) |
Properties reference
| Property | Type | Meaning |
|---|---|---|
currencyName | String | Display name, max 8 chars (e.g. "gems"). Used by name-based labels and messages. |
currencyCode | String? | Symbol/code (e.g. "$", "€"). null disables code-based styles. |
currencyCodePosition | RewardCurrencyCodePosition | Whether the code sits before or after the number. |
currencyPerDollar | Double | How many currency units one dollar is worth. |
userPayoutPercentage | Double | Fraction of gross value paid to the user, 0.4 … 1.0. |
rewardCurrencyLabelStyle | RewardCurrencyLabelStyle | How amounts are presented on reward labels. |
rewardCurrencyMessagingTextStyle | RewardCurrencyMessagingTextStyle | How the scan-toast copy phrases the reward. |
rewardRounding | RewardCurrencyRounding | Whole-number vs. two-decimal display. |
currencyImageLocations | Set<RewardCurrencyImageLocation> | Which surfaces show the coin icon. |
Display styles
Five enums control exactly how a reward renders.
RewardCurrencyLabelStyle — how reward labels read
| Value | Output | Notes |
|---|---|---|
CurrencyImage | 1,234 (+ coin icon) | Bare amount; icon drawn separately (see currencyImageLocations). |
CurrencyName | 1,234 gems | Amount 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; Trailing → 1,234€.
RewardCurrencyMessagingTextStyle — scan-toast copy
Controls the "Nice Scan!" toast description:
| Value | Example |
|---|---|
CurrencyName | You earned 250 points for this receipt! |
CurrencyCode | You earned $250 for this receipt! (leading) / You earned 250€ for this receipt! (trailing) |
NoAmount | You earned a reward for this receipt! |
RewardCurrencyRounding — number precision
| Value | Behavior | Example |
|---|---|---|
Whole | Ceils to the next whole unit. | 1,234 |
Decimal | Rounds 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):
| Value | Surface |
|---|---|
OfferWallItem | Offer cards on the offers wall. |
ReceiptTotalReward | The receipt summary's total-points header. |
ReceiptBoost | Boost claim buttons. |
ReceiptTask | Per-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 (1234567 → 1,234,567) and rounding is carry-safe (0.999 → 1.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:
| Surface | What it controls |
|---|---|
| Offer cards | Reward label per rewardCurrencyLabelStyle; icon per OfferWallItem. |
| Receipt header | Animated total; icon per ReceiptTotalReward. |
| Boost buttons | Boost reward label; icon per ReceiptBoost. |
| Task / product rows | Per-row reward label; icon per ReceiptTask. |
| Nice Scan toast | Copy 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.0 → 0.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
| Before | After |
|---|---|
client.rewardCurrencyName = "gems" | currencyName = "gems" |
client.rewardPayoutPercentage = 60.0 | userPayoutPercentage = 0.6 (÷100) |
client.rewardCurrencyPerDollar = 100.0 | currencyPerDollar = 100.0 |
reward.amount (raw number) | reward.amount.value (unwrap RewardPoint) |
ScanReward(reward = 250) | ScanReward(reward = RewardPoint(250.0)) |
RewardCurrency.icon = bytes | ActivationTheme.Icons.rewardIcon = ActivationIcon.Bytes(bytes) |