Skip to main content

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.

warning

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.

build.gradle.kts
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.

tip

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.registrationState is a StateFlow<RegistrationState>, with a synchronous isRegistered alongside 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)
}
}
}
StateMeaning
IdleNo registration has been attempted yet — identity or device configuration is still missing.
RegisteringA 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:

ExceptionWhat it means
MissingIdentityNeither hashedEmail nor hashedPhone has been set yet.
MissingDeviceConfigurationThe SDK has not yet received its device configuration.
FailedA real registration failure; wraps the underlying error.
note

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 typeWhen it fires
Rewards.BoostCreditEarnedThe rewarded ad completes and the credit is banked.
Rewards.BoostCreditAppliedCredit banked earlier lands on this receipt.
Rewards.BoostUnchanged. 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.

warning

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,
)
warning

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.

note

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.Ads from your testOptions set; TestOptions.Test now covers test promotions and test ad units.
  • Switch any positional ScanResults(...) or DuplicateDetection(...) construction to named arguments.
  • Add Rewards.BoostCreditEarned and Rewards.BoostCreditApplied branches to your rewards collector, banking the first and settling the second.
  • Optionally gate the offer wall on ActivationClient.registrationState (or isRegistered) instead of assuming registration has completed, and ignore the MissingIdentity / MissingDeviceConfiguration pre-login states.