Activation Android SDK 1.1.2
1.1.2 builds on 1.1.1. It adds an observable registration state so you can gate the offer wall on a real registration, hard currency mode for banked boost credits, and preloaded boost ads.
This release contains breaking changes. TestOptions.Ads was removed, two data classes gained constructor parameters, and the Rewards type gained two new subtypes. See Breaking changes and the Upgrade checklist.
Installation
Bump the BOM to 1.1.2. You still depend only on activation-bom and activation; everything else is pulled in automatically.
dependencies {
implementation(platform("com.actualplatform:activation-bom:1.1.2"))
implementation("com.actualplatform:activation")
}
The compile target is unchanged from 1.1.1: compileSdk 37 (or higher) is still required, and your app's targetSdk does not need to change. If you already upgraded to 1.1.1, there is nothing to do here.
The SDK ships its own consumer ProGuard/R8 rules and is safe under R8 full mode. If you previously added broad keep rules for Activation classes as a workaround, you can remove them.
What's new
- Observable registration state.
ActivationClient.registrationStateis aStateFlow<RegistrationState>, with a synchronousisRegisteredalongside it. Gate the offer wall on a successful registration instead of racing the SDK's debounce-and-network flow. See Registration state. - Hard currency mode. Where Google's rewarded-ad policy forbids paying out real money on the spot, rewarded boosts are banked and settled on a later scan, through two new reward types. See Hard currency mode.
- Preloaded boost ads. Boost and inline rewarded ads are preloaded when Receipt Summary appears, so tapping Boost shows the ad with no load step. The Boost button stays hidden until its ad is ready, and a failed preload drops the boost silently rather than reporting an error against something the shopper never acted on. No integrator setup is required.
- Fixed: clip and clipped icons. On the offers wall and offer-detail screen, the clip and clipped icons drew their checkmark and plus glyphs as transparent holes, letting the card background show through. Both now render as solid fills.
Registration state
The SDK registers a user once at least one of hashedEmail or hashedPhone is set, debouncing rapid property changes into a single network call. Until 1.1.2 there was no way to observe that — integrations had to assume registration had completed before presenting the offer wall.
ActivationClient.registrationState exposes it directly:
import com.actualplatform.activation.ActivationClient
import com.actualplatform.activation.RegistrationState
lifecycleScope.launch {
ActivationClient.instance.registrationState.collect { state ->
when (state) {
is RegistrationState.Idle,
is RegistrationState.Registering -> showLoading()
is RegistrationState.Registered -> showOfferWall() // state.clientId is available here
is RegistrationState.Exception -> handle(state)
}
}
}
| State | Meaning |
|---|---|
Idle | No registration has been attempted yet — identity or device configuration is still missing. |
Registering | A registration call is in flight. |
Registered(clientId) | Registration succeeded; the offer wall can be presented. |
Exception(...) | Registration did not complete. Carries an ActivationException.Registration. |
For a one-shot check outside a coroutine — for example, to decide whether to enable a "Earn Rewards" entry point — read the synchronous isRegistered:
if (ActivationClient.instance.isRegistered) {
earnRewardsButton.isEnabled = true
}
Registration exceptions
RegistrationState.Exception carries an ActivationException.Registration. Not all of them are errors worth showing a shopper:
| Exception | What it means |
|---|---|
MissingIdentity | Neither hashedEmail nor hashedPhone has been set yet. |
MissingDeviceConfiguration | The SDK has not yet received its device configuration. |
Failed | A real registration failure; wraps the underlying error. |
MissingIdentity and MissingDeviceConfiguration describe the normal pre-login state and reach the flow during ordinary startup. Do not surface them to shoppers — treat them as "not ready yet" and keep waiting. Only Failed represents an error worth logging or displaying.
Hard currency mode
Google's rewarded-ad policy forbids paying out real money at the moment an ad completes. For apps that reward shoppers in real currency, 1.1.2 adds hard currency mode: a Google rewarded boost is banked when the ad finishes, then settled against a later receipt scan.
The two halves are reported as separate reward types on the existing rewards flow:
| Reward type | When it fires |
|---|---|
Rewards.BoostCreditEarned | The rewarded ad completes and the credit is banked. |
Rewards.BoostCreditApplied | Credit banked earlier lands on this receipt. |
Rewards.Boost | Unchanged. CPA boosts are unaffected and keep paying out immediately. |
Hard currency mode is enabled per app by the Actual team. There is no host-app configuration, and no code is required to turn it on.
Even if hard currency mode is never enabled for your app, the new subtypes change the Rewards type itself. A Kotlin when (reward) written as an exhaustive expression over Rewards will no longer compile until you add branches for BoostCreditEarned and BoostCreditApplied. See New Rewards subtypes.
Because BoostCreditEarned banks a credit rather than paying it out, treat the two events differently in your ledger: BoostCreditEarned is a pending balance, and BoostCreditApplied is the settlement that credits the shopper against a specific receipt. Backend-calculated rewards continue to arrive through RewardUpdate webhooks, which remain the source of truth for crediting users.
Breaking changes
Three changes require attention when upgrading from 1.1.1.
TestOptions.Ads removed
Test ads are no longer a separate flag. TestOptions.Test now also registers the running device as a Google Ad Manager test device, so ad units serve test creatives instead of production ads.
import com.actualplatform.activation.networking.TestOptions
// Before (1.1.1)
ActivationClient.instance.testOptions = setOf(
TestOptions.Ads, // Use Google Ad Manager test ad units
TestOptions.Test, // Enable test mode (returns test promotions)
)
// After (1.1.2)
ActivationClient.instance.testOptions = setOf(
// Test mode: returns test promotions and registers this device
// as a Google Ad Manager test device.
TestOptions.Test,
)
These options are intended for development and testing only. Do not enable them in production builds.
Positional construction of ScanResults and DuplicateDetection
ScanResults and DuplicateDetection both gained parameters in the middle of their constructors, so code that builds them by position no longer lines up. Use named arguments:
// Before (1.1.1) — positional; the new parameters shift everything after them
ScanResults(retailerId, total, purchaseDate, /* … */)
// After (1.1.2) — named arguments
ScanResults(
retailerId = retailerId,
total = total,
purchaseDate = purchaseDate,
// …
)
Integrations that already build these types with named arguments need no source changes here.
New Rewards subtypes
Rewards.BoostCreditEarned and Rewards.BoostCreditApplied were added to the sealed Rewards type (see Hard currency mode). An exhaustive when over Rewards will not compile until it handles them:
import com.actualplatform.activation.ActivationClient
import com.actualplatform.activation.Rewards
LaunchedEffect(Unit) {
ActivationClient.instance.rewards.collect { reward ->
when (reward) {
is Rewards.ScanFinished -> credit(reward.amount.value, type = "scan")
is Rewards.Promotion -> credit(reward.amount.value, type = "promo")
is Rewards.Boost -> credit(reward.amount.value, type = "boost")
// New in 1.1.2
is Rewards.BoostCreditEarned -> bank(reward.amount.value)
is Rewards.BoostCreditApplied -> settle(reward.amount.value)
}
}
}
If your app will never run in hard currency mode, an else -> {} branch is sufficient — but handling both explicitly keeps you correct if the mode is enabled for your app later.
The rewards flow and RewardPoint are Kotlin-first: RewardPoint is a value class whose accessors are name-mangled and not practical to read from Java. Handle rewards in Kotlin, or expose a small Kotlin shim that returns a plain double.
Upgrade checklist
- Bump the BOM to
1.1.2. - Remove
TestOptions.Adsfrom yourtestOptionsset;TestOptions.Testnow covers test promotions and test ad units. - Switch any positional
ScanResults(...)orDuplicateDetection(...)construction to named arguments. - Add
Rewards.BoostCreditEarnedandRewards.BoostCreditAppliedbranches to your rewards collector, banking the first and settling the second. - Optionally gate the offer wall on
ActivationClient.registrationState(orisRegistered) instead of assuming registration has completed, and ignore theMissingIdentity/MissingDeviceConfigurationpre-login states.