Skip to Content

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 cooldown

The primer emits the same six canonical events as the iOS SDK, so reporting is identical across platforms:

EventFired when
in_app.displayedThe primer card is shown
in_app.dismissedThe card is dismissed without a choice
push_primer.allow_tappedUser taps Allow
push_primer.later_tappedUser taps Not right now
push_primer.permission_grantedThe OS permission was granted (or pre-13 default-on)
push_primer.permission_deniedThe 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 for

Respect 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)) return

Show 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_NOTIFICATIONS prompt via ActivityCompat.requestPermissions, using POST_NOTIFICATIONS_REQUEST_CODE.
  • Pre-Android 13. Notifications are granted by default, so there’s no runtime prompt to show. allowTapped short-circuits and emits push_primer.permission_granted immediately.

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()

What’s next