Android Appearance Customization
Activation.theme controls how the SDK looks SDK-wide; Activation.appearance layers per-screen overrides on top. Where the theme is a flat catalogue of ~20 semantic color roles that cascade everywhere, the appearance is organised per screen, and each screen exposes two kinds of override:
- Labels — the wording shown on a screen (e.g. the scan-receipt FAB text).
String?;null→ the SDK's bundled, localized default. - Colors — fine-grained, per-element color overrides (e.g. just the offer-card background, or just the receipt-summary header band).
Long?(0xAARRGGBB);null→ the exactActivationThemetoken that element already renders with.
Theme first, appearance second: set your brand roles on Activation.theme and the whole SDK follows. Reach for appearance colors only when you need to override one specific element beyond what the shared roles give you — a host that sets nothing sees pixel-identical rendering to the un-customised SDK.
Activation.appearance = ActivationAppearance(
offersWall = ActivationAppearance.OffersWall(
colors = ActivationAppearance.OffersWall.Colors(
offerWallBackground = 0xFFFFFFFF, // override just this screen's background
),
labels = ActivationAppearance.OffersWall.Labels(
scanExtendedLabel = "Scan your receipt",
),
),
)
Like the theme, Activation.appearance is settable from any thread, any time, with no Compose import — and updates propagate live to every SDK screen currently composed.
Structure
The appearance is a nested tree: one member per screen. Every screen carries a Colors record; OffersWall additionally carries a Labels record. Every field defaults, so you override only what you need:
data class ActivationAppearance(
val offersWall: OffersWall = OffersWall(),
val loading: Loading = Loading(),
val errorModal: ErrorModal = ErrorModal(),
val receiptSummary: ReceiptSummary = ReceiptSummary(),
val missedEarnings: MissedEarnings = MissedEarnings(),
) {
data class OffersWall(val colors: Colors = Colors(), val labels: Labels = Labels()) {
data class Labels(val scanLabel: String? = null, val scanExtendedLabel: String? = null)
data class Colors(/* per-element Long? overrides — see tables below */)
}
data class Loading(val colors: Colors = Colors()) { data class Colors(/* … */) }
data class ErrorModal(val colors: Colors = Colors()) { data class Colors(/* … */) }
data class ReceiptSummary(val colors: Colors = Colors()) { data class Colors(/* … */) }
data class MissedEarnings(val colors: Colors = Colors()) { data class Colors(/* … */) }
}
Labels
Labels for the offers-wall scan-receipt FAB. Each field is a String? defaulting to null, meaning "use the SDK's built-in localized default" — set one only to override the bundled string.
offersWall.labels.scanLabel
Type: String? · Default: null
Label on the scan-receipt FAB in its normal (collapsed) state. null falls back to the SDK's bundled, localized default.
offersWall.labels.scanExtendedLabel
Type: String? · Default: null
Label on the scan-receipt FAB in its extended (expanded) state. null falls back to the SDK's bundled, localized default.
The bundled defaults are localized by the SDK. A value you set here is shown verbatim in every locale — supply your own localized string if you override it.
Colors — The Override Model
Every appearance color is a Long? in 0xAARRGGBB form (the same encoding as theme colors):
null(default) → the element renders with the exactActivationTheme.Colorstoken it uses today. The fallback is baked in per key (see the tables below) — it is not always the same token, it's whatever that specific element currently reads.- A value → applied as-is. You control alpha through the
0xAAbyte.
This makes the override model non-destructive by construction: the fallback for every key reproduces current rendering — including alpha-composed treatments like the loading progress track (textInverse at 16% alpha) or the edited-row highlight (success at 10% alpha) — so leaving a key unset can never change a pixel.
Because the fallback is a theme token, appearance colors and theme roles compose cleanly: override primary on the theme and every element keyed to it (including appearance-color fallbacks) follows; then set one specific element's appearance color to peel it off that role.
Colors vs. theme roles — which do I set?
| Goal | Set |
|---|---|
| Rebrand the whole SDK | Activation.theme color roles |
| Change one element on one screen | Activation.appearance … .colors |
Colors by Screen
Each table lists Field · Fallback (the theme role used when null) · Element. All fields are Long?, default null.
offersWall.colors
Covers the Offer Wall list, Offer Card (grid + list variants), Offer Details, the Clip/Clipped state, and the Stores sheet.
Offer Wall
| Field | Fallback | Element |
|---|---|---|
offerWallBackground | background | Screen background (Scaffold + top-app-bar) |
offerWallSectionHeaderLabel | textPrimary | Section header title |
offerWallSectionHeaderShowMoreIcon | accent | Section "show more" icon (built-in fallback painter) |
offerWallSectionHeaderShowMoreBackground | surfaceAccent | Section "show more" button container |
offerWallFloatingButtonBackground | primary | Scan-receipt FAB container |
offerWallFloatingButtonLabel | textOnPrimary | Scan-receipt FAB label + icon tint |
offerWallMoreMerchantsIcon | tint-only | "More merchants" chip icon — see Special cases |
Offer Card (grid + list)
| Field | Fallback | Element |
|---|---|---|
offerBackground | surface | Card container |
offerBrandLabel | textPrimary | Brand / product title |
offerEligibleMerchantsLabel | textAccent | "Eligible at …" line |
offerRewardPointsLabel | textAccent | Reward-points amount |
offerTagLabel | tagText | "Buy N" tag text |
offerTagBackground | tagSurface | "Buy N" tag pill background |
Clip / Clipped state
| Field | Fallback | Element |
|---|---|---|
offerClipButtonIcon | primary | Unclipped clip-action icon (card + details) |
offerClipButtonBackground | surface | Unclipped clip-action container |
offerClippedButtonIcon | success | Clipped-state icon |
offerClippedButtonBackground | surface | Clipped-state container |
offerClippedToastMessageLabel | textPrimary | "Offer clipped" toast / snackbar text |
offerClippedToastMessageBackground | surface | "Offer clipped" toast / snackbar surface |
Offer Details
| Field | Fallback | Element |
|---|---|---|
offerDetailsTitleLabel | textPrimary | Title |
offerDetailsEarnRewardLabel | textAccent | Earn / short-description subtitle |
offerDetailsClipLabel | textPrimary | "CLIP" action label |
offerDetailsSectionHeaderTitleLabel | textPrimary | Section headers (How to Qualify / Eligible Products / Valid Stores / Qualifying Details) |
offerDetailsSectionHeaderToggleLabel | textAccent | Section see-all / read-more toggles |
offerDetailsSectionBodyLabel | textPrimary | Section body text |
offerDetailsFinePrintLabel | textPrimary | Qualifying Details full terms + bullets |
offerDetailsTagChipLabel | textSecondary | Eligible-products tag chip text |
offerDetailsTagChipBorder | border | Eligible-products tag chip border |
offerDetailsSectionNumberedListBadgeLabel | textAccent | How-to-Qualify step-number text |
offerDetailsSectionNumberedListBadgeBackground | surfaceAccent | How-to-Qualify step-number circle |
Stores
| Field | Fallback | Element |
|---|---|---|
storesHeaderBackground | background | Stores sheet + header background |
storesHeaderTitleLabel | textPrimary | Sheet header title |
storesListBackground | background | Stores list background |
storesListSectionHeaderLabel | textPrimary | List section header |
storesListItemBackground | background | Store card container |
storesListItemDefaultIcon | textSecondary | Store card fallback icon (no logo) |
storesListItemTitleLabel | textPrimary | Store name |
storesListItemSubtitleLabel | textSecondary | Offer-count subtitle |
loading.colors
The ad-loading sheet.
| Field | Fallback | Element |
|---|---|---|
adLoadingLoadingBarLabel | textInverse | Progress-row label |
adLoadingLoadingBarBackground | textInverse @ 16% | Progress-bar track |
adLoadingLoadingBarProgress | primary | Progress-bar fill |
adLoadingDefaultTitleLabel | textInverse | Staged headline |
adLoadingDefaultDescriptionLabel | textInverse @ 70% | Staged description |
errorModal.colors
The scan-error modal.
| Field | Fallback | Element |
|---|---|---|
errorModalBackground | background | Dialog surface |
errorModalIconBackground | warning | Error-icon tint (built-in glyph; the SDK draws a bare glyph, no circle behind it) |
errorModalTitleLabel | textPrimary | Title |
errorModalDescriptionLabel | textPrimary | Message |
errorModalBackButtonLabel | textPrimary | Dismiss-button content |
receiptSummary.colors
The post-scan Receipt Summary, plus the shared purchase-row text colors reused by Missed Earnings rows.
| Field | Fallback | Element |
|---|---|---|
postScanHeaderBackground | surfaceBrand | Header band (+ slab gradient tail + status-bar filler) |
postScanTotalPointsBackground | secondary | Total-points pill background |
postScanTotalPointsLabel | textOnSecondary | Total-points pill text |
postScanReceiptButtonIcon | textOnSecondary | Missed-earnings entry icon (built-in fallback) |
postScanReceiptButtonBackground | secondary | Missed-earnings entry icon circle |
postScanFooterBackground | surface | Footer row background |
postScanFooterButtonTitle | textAccent | "Continue" footer-button content |
postScanMerchantNameLabel | textPrimary | Store name |
postScanTripInfoLabel | textPrimary | Trip info (date / time) |
postScanSectionHeaderTitleLabel | textSecondary | Row-group section headers |
postScanNoBoostsLabel | textPrimary | "No boosts" empty-state message |
postScanSuccessTitleLabel | textPrimary | "Nice scan" card title |
postScanSuccessDescriptionLabel | textPrimary | "Nice scan" card description |
postScanBoostTitleLabel | textPrimary | CPA dialog title |
postScanBoostDescriptionLabel | textPrimary | CPA dialog subtitle |
postScanBoostSkipButtonLabel | primary | CPA "No Thanks" content |
postScanBoostClaimButtonLabel | textOnPrimary | Ad-boost / CPA "Redeem" content |
postScanBoostClaimButtonIcon | warning | Ad-boost icon (built-in fallback) |
postScanBoostClaimButtonBackground | primary | Ad-boost / CPA "Redeem" container |
postScanPurchasePointsLabel | primary | Earned-reward points label |
postScanPurchaseBackground | surface | Standard purchase-row card |
postScanQualifiedPurchaseBackground | surface | Qualified (earned-reward) row card |
postScanPurchaseInfoIcon | border | Inline-task info-icon fallback tint |
postScanInlineProductTaskBackground | surfaceAccent | Inline product-task row card |
postScanInlineProductTaskScanAndEarnBackground | primary | "Scan & earn" pill background |
postScanInlineProductTaskWatchAndEarnBackground | primary | "Watch & earn" pill background |
postScanInlineProductTaskScanAndEarnLabel | textInverse | "Scan & earn" pill text (+ leading icon — see Special cases) |
postScanInlineProductTaskWatchAndEarnLabel | textInverse | "Watch & earn" pill text (+ leading icon) |
postScanInlineProductTaskPointsLabel | primary | Inline-task reward-points label |
purchaseRowLabelColor | textPrimary | Product name — all row types + Missed Earnings (shared) |
purchaseRowMetadataLabelColor | textPrimary | Product metadata line — all row types + Missed Earnings (shared) |
missedEarnings.colors
The Missed Earnings list, its edit dialogs, and the missed-earnings alert modal.
| Field | Fallback | Element |
|---|---|---|
missedEarningsNavigationTitleLabel | textPrimary | Top-bar title |
missedEarningsNavigationDescriptionLabel | textPrimary | Top-bar subtitle |
missedEarningsNavigationBarText | textPrimary | Back-icon fallback tint (in-sheet only) |
missedEarningsNavigationEditButtonIcon | accent | Edit-mode toggle icon (built-in fallback) |
missedEarningsNavigationEditButtonBackground | surfaceAccent | Edit-mode toggle chip |
missedEarningsNavigationSaveButtonIcon | tint-only | Save icon — see Special cases |
missedEarningsNavigationSaveButtonBackground | primary | Save chip |
missedEarningsFieldEditIcon | accent | Per-row edit-pencil icon |
missedEarningsAddNewFieldLabel | primary (text) / accent (icon) | "Add missing product" row — see Special cases |
missedEarningsModifiedFieldBackground | success @ 10% | Edited / new-product row highlight |
missedEarningsListSectionTitleLabel | textPrimary | List section title |
missedEarningsTripItemLabel | textPrimary | Trip / field-row value |
missedEarningsEditModalBackground | background (merchant) / surface (date) | Edit-dialog card — see Special cases |
missedEarningsEditModalTitleLabel | textPrimary | Edit-dialog title |
missedEarningsEditModalInputLabel | textPrimary | Edit-dialog field label |
missedEarningsEditModalInputPlaceholderLabel | textSecondary | Edit-dialog field placeholder |
missedEarningsEditModalInputValueLabel | textPrimary | Edit-dialog field value |
missedEarningsEditModalCancelButtonLabel | primary | Dialog "Cancel" content |
missedEarningsEditModalSaveButtonLabel | textOnPrimary | Dialog "Save" content |
missedEarningsEditModalSaveButtonBackground | primary | Dialog "Save" container |
missedEarningsEditModalDatePicker | primary | Date-picker selected day + today's-date border |
missedEarningsAlertTitleLabel | textPrimary | Alert-modal title |
missedEarningsAlertMessageLabel | textPrimary | Alert-modal message |
Special cases
- Tint-only-when-set —
offerWallMoreMerchantsIconandmissedEarningsNavigationSaveButtonIconback elements that are untinted today. Theirnulldefault leaves the glyph untinted (it does not fall back to a theme token); set a value to tint it. Neither ever tints a host-suppliedtheme.iconsimage — only the built-in fallback glyph. - One key, two fallbacks — a few keys color two elements that currently use different tokens.
missedEarningsAddNewFieldLabelcolors the row text (primary) and its icon (accent);missedEarningsEditModalBackgroundbacks the merchant dialog (background) and the date dialog (surface); the inline-task pill labels also tint a leading icon (primary), distinct from the pill text (textInverse). Setting the key overrides both sites; leaving itnullpreserves each element's own current color. - Shared purchase-row keys —
purchaseRowLabelColor/purchaseRowMetadataLabelColorcolor the product name / metadata line across all three Receipt Summary row types and the Missed Earnings product rows.
Putting It Together
Per-screen overrides layered on top of a brand theme:
// Application.onCreate — brand roles first
Activation.theme = ActivationTheme(
lightColors = ActivationColorDefaults.lightColors(
primary = 0xFF7C3AED,
textAccent = 0xFF6D28D9,
surfaceAccent = 0xFFF3E8FF,
),
)
// Per-screen overrides — layered on top of the theme
Activation.appearance = ActivationAppearance(
offersWall = ActivationAppearance.OffersWall(
colors = ActivationAppearance.OffersWall.Colors(
offerWallBackground = 0xFFFAF5FF, // just this screen's background, distinct from `background`
),
labels = ActivationAppearance.OffersWall.Labels(
scanLabel = "Scan",
scanExtendedLabel = "Scan your receipt",
),
),
receiptSummary = ActivationAppearance.ReceiptSummary(
colors = ActivationAppearance.ReceiptSummary.Colors(
postScanHeaderBackground = 0xFFFFFFFF, // white header band, while buttons stay purple
),
),
)
Result: the theme rebrands the whole SDK purple; on top of that, the offers wall gets custom scan-FAB wording and a tinted background, and the receipt-summary header band is forced white — both without disturbing the purple primary role everywhere else.
FAQ
What's the difference between Activation.theme and Activation.appearance?
theme sets ~20 semantic color roles (plus shapes, typography, icons) that cascade SDK-wide. appearance applies per-screen overrides on top — both text labels and fine-grained per-element color overrides. Rebrand with theme; reach for appearance colors when you need to change one specific element beyond what the shared roles allow. They're independent hooks; set either, both, or neither.
When should I set an appearance color instead of a theme color?
Set a theme color to move a semantic role everywhere it's used (e.g. primary recolors every primary button, FAB, and progress bar at once). Set an appearance color to override a single element on a single screen without disturbing that role elsewhere — e.g. make just the receipt-summary header band white (receiptSummary.colors.postScanHeaderBackground) while your brand color stays on buttons. Every appearance color defaults to the theme token its element already uses, so unset keys change nothing.
Why are appearance labels and colors nullable?
null means "use the default." For labels, that's the SDK's built-in localized default — a value you supply is shown verbatim in every locale, so provide your own localized string if you override. For colors, null means "use the theme token this element already renders with," so unset keys can never change a pixel.
What happens if I change Activation.appearance after the SDK is on screen?
Every SDK screen currently composed recomposes with the new values. There's no flush, no reset, no lifecycle hook to call — the same live-update behavior as Activation.theme.
Do I have to import Compose to use this?
No. Activation.appearance is a plain Kotlin object — labels are String? and colors are Long?. Set it from Application.onCreate, a ViewModel, or anywhere else.