Skip to main content

Android Theme Customization

The Activation SDK exposes a single styling hook — Activation.theme — that drives every visual aspect of every SDK screen: brand palette, type scale, corner-radius unit, and every customisable icon. Set it once during app startup (or anytime later) and updates propagate live to every SDK screen currently composed:

Activation.theme = ActivationTheme(
colors = ActivationTheme.Colors(primary = 0xFF7C3AED),
icons = ActivationTheme.Icons(
scanIcon = ActivationIcon.Bytes(myScanIconBytes),
),
)

You don't need to import Compose or wrap anything — the SDK handles composition and dark/light resolution internally.


Quick Start

Minimal customization — set your brand color and every primary button, FAB, link, and accent surface across the SDK updates:

// Application.onCreate (or any time afterwards)
Activation.theme = ActivationTheme(
colors = ActivationTheme.Colors(
primary = 0xFF7C3AED, // brand purple
textAccent = 0xFF6D28D9, // links / highlights
surfaceAccent = 0xFFF3E8FF, // soft tinted panels
),
)

Setting the Theme

The theme is settable from any thread, any time:

// Set once at startup
Activation.theme = ActivationTheme(
colors = ActivationTheme.Colors(primary = 0xFF0062F2),
typography = ActivationTheme.Typography(sizeScaleFactor = 1.1f),
)

// Update later — propagates live
Activation.theme = Activation.theme.copy(
colors = Activation.theme.colors.copy(primary = 0xFFFF6B00),
)

There's no init() call, no reset, and no lifecycle to manage.


ActivationTheme — Global Palette

The theme defines four token families: colors, shapes, typography, icons. All four ship with sensible defaults — only override the ones you need:

data class ActivationTheme(
val colors: Colors = Colors(),
val darkColors: Colors? = null, // optional dark variant
val shapes: Shapes = Shapes(),
val typography: Typography = Typography(),
val icons: Icons = Icons(),
)

Colors

14 brand and semantic color tokens, each encoded as a Long (0xAARRGGBB). Defaults form the SDK's Cobalt brand palette.

primary

Type: Long · Default: 0xFF0062F2

Primary brand color — primary button backgrounds, action icons, pagination indicators, static icons, the progress bar, primary headers, and primary tags.

secondary

Type: Long · Default: 0xFF0062F2

Secondary button backgrounds.

background

Type: Long · Default: 0xFFFFFFFF

App background — the bottom layer behind every screen.

surface

Type: Long · Default: 0xFFFCFBFA

Surface background — cards, modals, in-app notifications.

surfaceAccent

Type: Long · Default: 0xFFEBF2FE

Accent background — reward cards, icon backgrounds, soft highlight panels.

surfaceInverse

Type: Long · Default: 0xFF262626

Inverse surface — for elements that need to break out of the main palette.

textPrimary

Type: Long · Default: 0xFF142641

Primary text — main body copy and headings.

textSecondary

Type: Long · Default: 0xFF9CA3AF

Secondary text — descriptions, supporting copy, helper text.

textAccent

Type: Long · Default: 0xFF004EC2

Accent text — links, highlighted text, secondary emphasis.

textInverse

Type: Long · Default: 0xFFFFFFFF

Inverse text — text on top of primary / saturated brand surfaces.

success

Type: Long · Default: 0xFF29CC6A

Success state — clipped icons, check marks, success borders, positive feedback.

error

Type: Long · Default: 0xFFF43F5E

Error state — validation failures, destructive actions.

warning

Type: Long · Default: 0xFFFCA355

Warning state — non-blocking cautions, attention prompts.

border

Type: Long · Default: 0xFFE5E7EB

Border / structural divider — card borders, field borders, hairline separators.


Shapes

Two tunable values control the entire corner-radius and border-width scale.

unit

Type: Float · Default: 8f (dp)

Base corner unit. Every corner radius in the SDK is a fixed multiple of this value:

StepMultiplierValue at default
xs0.5×4 dp
sm1.0×8 dp
md1.5×12 dp
lg2.0×16 dp
xl3.0×24 dp
pillfully rounded

Set unit = 0f for sharp corners, unit = 12f for a softer feel — the whole scale follows.

borderWidthDp

Type: Float · Default: 1f (dp)

Width applied to card borders, field outlines, and hairline separators across the SDK.


Typography

The SDK ships a Material 3–compatible type ramp (displayLargelabelSmall) and exposes two tuning knobs.

sizeScaleFactor

Type: Float · Default: 1.0f

Multiplier applied to every text style's font size and line-height. 1.0f = design defaults; 1.1f → 10% larger across the board; 0.9f shrinks the entire ramp.

fontFamily

Type: FontFamily? (from androidx.compose.ui.text.font) · Default: null

Font applied to every SDK text style. null falls back to the Android system font (Roboto).

Activation.theme = ActivationTheme(
typography = ActivationTheme.Typography(
fontFamily = FontFamily(ResourcesCompat.getFont(context, R.font.brand)),
),
)

fontFamily is the one place in the styling API where host code touches a Compose type directly — every other ActivationTheme field is a plain Kotlin primitive. Leave it null if you want to keep your host module Compose-free.

The full type ramp:

StyleDefault size / line-heightWeight
displayLarge57 / 64Normal
displayMedium45 / 52Normal
displaySmall36 / 44Normal
headlineLarge32 / 40Normal
headlineMedium28 / 36Normal
headlineSmall24 / 32Normal
titleLarge22 / 28Normal
titleMedium16 / 24Medium
titleSmall14 / 20Medium
bodyLarge16 / 24Normal
bodyMedium14 / 20Normal
bodySmall12 / 16Normal
labelLarge14 / 20Medium
labelMedium12 / 16Medium
labelSmall11 / 16Medium

Icons

Theme-level icons that apply across all SDK screens. All fields default to null — only set the ones you want to override.

rewardIcon

Type: ActivationIcon? · Default: null

The icon shown next to reward / point values throughout the SDK. null falls back to the SDK's default reward glyph.

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

scanIcon

Type: ActivationIcon? · Default: null

Icon shown on the scan-receipt FAB. null falls back to the SDK's default scan glyph.

Activation.theme = ActivationTheme(
icons = ActivationTheme.Icons(
scanIcon = ActivationIcon.Bytes(myScanIconBytes),
),
)

Dark Mode (darkColors)

ActivationTheme accepts a second Colors instance for dark mode. When darkColors is set and the system is in dark mode, the SDK uses darkColors everywhere colors would otherwise be read. When darkColors is null, the SDK uses colors regardless of system theme.

Activation.theme = ActivationTheme(
colors = ActivationTheme.Colors(
primary = 0xFF0062F2,
background = 0xFFFFFFFF,
),
darkColors = ActivationTheme.Colors(
primary = 0xFF3B82F6, // lightened so it stays vibrant on dark
background = 0xFF0A1426, // deep blue surface
textPrimary = 0xFFF3F4F6,
textInverse = 0xFF142641,
),
)

Putting It Together

A realistic configuration mixing colors, dark colors, shapes, typography, and icons:

// Application.onCreate
Activation.theme = ActivationTheme(
colors = ActivationTheme.Colors(
primary = 0xFF7C3AED,
secondary = 0xFF7C3AED,
textAccent = 0xFF6D28D9,
surfaceAccent = 0xFFF3E8FF,
),
darkColors = ActivationTheme.Colors(
primary = 0xFF8B5CF6,
background = 0xFF0F0A1A,
surface = 0xFF1A1130,
textPrimary = 0xFFEDE9FE,
textInverse = 0xFF1A1130,
),
shapes = ActivationTheme.Shapes(unit = 12f),
typography = ActivationTheme.Typography(sizeScaleFactor = 1.05f),
icons = ActivationTheme.Icons(
rewardIcon = ActivationIcon.Bytes(loadAsset("reward_glyph.svg")),
scanIcon = ActivationIcon.Bytes(loadAsset("scan_icon.svg")),
),
)

Result: the brand swaps from Cobalt to a custom purple with a softer dark-mode variant, slightly larger type, a 12 dp corner unit, and overridden reward / scan glyphs.


Best Practices

Set the theme once

Activation.theme is designed to be set during app startup. You can mutate it anytime — updates are live — but treating it as configuration rather than runtime state keeps the model simple.

Define ActivationTheme.darkColors for a dark-mode palette

The SDK swaps to it automatically under isSystemInDarkTheme(). If you don't ship one, the SDK uses colors in both schemes.

Encode Long colors as 0xAARRGGBB

Always include the alpha byte (0xFF… for fully opaque). The SDK expects the standard packed Android color encoding.

Test both schemes

When shipping a custom palette, render the offers wall in both light and dark mode. The theme's textInverse token is white by default — if your primary is light, white-on-light will fail contrast.


FAQ

Do I have to import Compose to use this?

No. Activation.theme is a plain Kotlin object. Set it from Application.onCreate, a ViewModel, or anywhere else. The one exception is Typography.fontFamily, which is a Compose FontFamily type — leave it null if you want to keep your host module Compose-free.

What happens if I change Activation.theme after the SDK is on screen?

Every SDK screen currently composed recomposes with the new tokens. There's no flush, no reset, no lifecycle hook to call.

Why are ActivationTheme.Colors fields Long instead of Color?

So host code can configure them without depending on Compose. Plain Long values can be passed across module boundaries and through DI containers without dragging in any UI framework types.

Why are theme icons nullable?

null means "use the SDK's built-in default." Only set a field when you want to override the default.

Where do I put font overrides?

Set ActivationTheme.Typography.fontFamily directly with a Compose FontFamily, e.g. FontFamily(ResourcesCompat.getFont(context, R.font.inter)) or FontFamily(Font(R.font.inter)). Leaving it null keeps the Android system font (Roboto).

Is the SDK offer wall accessible (a11y)?

The internal SDK screens use platform-native semantics (Compose Modifier.semantics). The default theme passes WCAG AA contrast on key surfaces; if you customise heavily, validate contrast for primary / textInverse and surfaceAccent / textAccent pairs in particular.