Push primer (soft-ask)
The push primer is a soft-ask in-app card the Active Reach Android SDK shows before the system notification prompt. Instead of letting Android’s one-shot POST_NOTIFICATIONS dialog fire cold, you first present an in-app card that explains why notifications are worth allowing. Only users who tap Allow on your card ever see the OS prompt — so you never burn the single system opportunity on a user who would have declined.
Operators author the card as an in-app campaign with render type push_primer; the SDK exposes the InAppPushPrimer class (package ai.aegis.sdk.inapp) to drive its lifecycle and bridge into the Android permission flow.
Why soft-ask
On Android the runtime notification prompt is effectively one-shot — once a user denies it, you can’t re-present the system dialog without sending them into Settings. A cold prompt forces the decision with no context. The soft-ask card lets you explain the value first, in your own UI, and only escalate to the OS prompt for users who’ve already said yes in-app. Users who tap Not right now enter a cooldown instead of a hard denial, so you can ask again later.
The flow
in-app card (push_primer)
├── Allow → fire runtime POST_NOTIFICATIONS prompt (Android 13+)
│ ├── granted → push_primer.permission_granted
│ └── denied → push_primer.permission_denied
├── Later → push_primer.later_tapped + start cooldown
└── Dismiss → in_app.dismissed + start cooldownThe primer emits the same six canonical events as the iOS SDK, so reporting is identical across platforms:
| Event | Fired when |
|---|---|
in_app.displayed | The primer card is shown |
in_app.dismissed | The card is dismissed without a choice |
push_primer.allow_tapped | User taps Allow |
push_primer.later_tapped | User taps Not right now |
push_primer.permission_granted | The OS permission was granted (or pre-13 default-on) |
push_primer.permission_denied | The OS permission was denied |
Construct the primer
import ai.aegis.sdk.Aegis
import ai.aegis.sdk.inapp.InAppPushPrimer
val primer = InAppPushPrimer(
context = this,
emit = { eventName, properties ->
Aegis.track(eventName, properties)
},
)The constructor takes two arguments: the context and an emit lambda of type (eventName: String, properties: Map<String, Any>) -> Unit. The primer calls emit for every lifecycle event — route it into Aegis.track so the events reach ingestion.
The Config
Each primer invocation is driven by an InAppPushPrimer.Config, hydrated from the in-app campaign your operator authored:
val config = InAppPushPrimer.Config(
campaignId = "camp_abc123",
title = "Stay in the loop",
body = "Get order updates and member-only offers.",
allowButtonLabel = "Allow notifications",
laterButtonLabel = "Not right now",
imageUrl = null, // optional hero image
icon = null, // optional icon
cooldownDays = InAppPushPrimer.DEFAULT_COOLDOWN_DAYS,
)DEFAULT_COOLDOWN_DAYS is used when the campaign config doesn’t specify a cooldown.
Wire the lifecycle
The SDK doesn’t render the card for you — you own the UI. Call the lifecycle hooks from your view layer as the user interacts with it.
Skip if already authorized
Before showing anything, check whether notifications are already allowed. isAlreadyAuthorized() returns true when POST_NOTIFICATIONS is granted on Android 13+, or when the device is below Android 13 (no runtime permission exists there).
if (primer.isAlreadyAuthorized()) return // nothing to ask forRespect the cooldown
If the user previously tapped Not right now (or dismissed), don’t re-prompt until the cooldown elapses:
if (primer.isInCooldown(cooldownDays = config.cooldownDays)) returnShow the card and record the display
Render your card, then tell the SDK it’s on screen — this fires in_app.displayed:
showMyPrimerCard(config)
primer.displayed(config)Handle the buttons
// User tapped "Allow"
allowButton.setOnClickListener {
primer.allowTapped(activity = this, config = config)
}
// User tapped "Not right now"
laterButton.setOnClickListener {
primer.laterTapped(config) // emits push_primer.later_tapped + starts cooldown
dismissMyPrimerCard()
}
// User swiped/closed the card
onCardDismissed = {
primer.dismissed(config) // emits in_app.dismissed + starts cooldown
}Android 13+ runtime permission
allowTapped(activity, config) is where the SDK bridges into the Android permission system:
- Android 13+ (Tiramisu). It fires the runtime
POST_NOTIFICATIONSprompt viaActivityCompat.requestPermissions, usingPOST_NOTIFICATIONS_REQUEST_CODE. - Pre-Android 13. Notifications are granted by default, so there’s no runtime prompt to show.
allowTappedshort-circuits and emitspush_primer.permission_grantedimmediately.
On pre-Android-13 devices notifications are on by default unless the user turned them off in system settings. The primer is still useful there — operators use it to re-engage users who disabled notifications after install.
Route the result back from your Activity
Because allowTapped calls requestPermissions, the system delivers the outcome to your Activity’s onRequestPermissionsResult. You must forward that result back into the SDK so it can emit the granted/denied event:
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray,
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == InAppPushPrimer.POST_NOTIFICATIONS_REQUEST_CODE) {
primer.onPermissionResult(config, grantResults)
}
}onPermissionResult inspects grantResults and emits push_primer.permission_granted or push_primer.permission_denied accordingly.
Cooldown management
Both Not right now (laterTapped) and a plain dismiss (dismissed) start a cooldown so you don’t immediately re-prompt. Use isInCooldown(nowMillis, cooldownDays) to gate display. To reset the cooldown manually — for example when a user opts back in from a settings screen — call:
primer.clearCooldown()