Supported on: Android SDK
1.0.33+Present a Paywall
Present a paywall by its identifier:Present by Placement
Present a paywall by placement — the SDK uses audience rules configured in the Console to determine which paywall to show:presentPaywallByPlacement is a top-level method on AppDNA (not on AppDNA.paywall).
Module Access
Access the paywall module directly:Module Methods
The placement-routed flavor lives on the top-level
AppDNA object as AppDNA.presentPaywallByPlacement(activity, placement, context).
PaywallContext
Provide context about where and why the paywall is being shown:The
placement value is included in all paywall analytics events, allowing you to measure conversion by placement in the Console.AppDNAPaywallDelegate
Implement the delegate interface to receive paywall lifecycle callbacks. The same delegate fires for paywalls launched standalone viaAppDNA.paywall.present(...) AND for paywalls launched from inside an onboarding flow (via the present_paywall step outcome) — register once with AppDNA.paywall.setDelegate(...) and handle every paywall presentation through the same handler.
Example Implementation
Restore Purchases
The Restore button is rendered alongside the CTA section (above or below the main subscribe button, controlled by the section’srestore_text and restore_position config in the Console). When the user taps it, the SDK runs the restore flow and fires the lifecycle:
- SDK fires
onPaywallRestoreStarted(paywallId)immediately. - SDK calls
BillingClient.queryPurchasesAsync(SUBS)+queryPurchasesAsync(INAPP)and queries your verification endpoint for previously-purchased products (both subscriptions and one-time products are restorable). - On success:
onPaywallRestoreCompleted(paywallId, productIds)fires with the list of restored product identifiers, and the SDK emits apurchase_restoredanalytics event automatically. - On failure (network error, no previous purchases, Play Billing error):
onPaywallRestoreFailed(paywallId, error)fires and the SDK emits apurchase_restore_failedevent.
shouldDismiss = productIds.isNotEmpty() && !hostRequestedSkip) — the user is now entitled, so there is nothing left to buy. The paywall stays visible only when the restore returns no products, fails, or the host opted out of auto-dismiss via skipNextAutoDismissOnRestore.
For richer transaction details (status, expiry, store), call
AppDNA.billing.getEntitlements() after a successful restore — it returns List<Entitlement> with the full verified state. The paywall delegate intentionally returns just product IDs to keep the callback lightweight.PaywallAction
ThePaywallAction enum represents user interactions within the paywall:
DismissReason
TheDismissReason enum indicates how the paywall was closed. The wire-format string (sent in paywall_close analytics) is shown in the right column — same value across iOS, Android, Flutter, and React Native.
Paywall Sections
Paywalls configured in the Console support the following content sections:Plan Display Styles
The plan selection section supports the following display styles, configured per paywall in the Console as a lowercaseplan_display_style string:
Card & Badge Customization
Plan cards support per-plan styling configured in the Console:- Badge text and color — e.g., “Best Value”, “Most Popular” with custom background
- Card border and shadow — highlight the selected or recommended plan
- Save percentage — automatically calculated and displayed on annual plans
- Trial label — shows “7-day free trial” or custom trial messaging
- Card background — solid color, gradient, or image per plan card
- Corner radius — configurable per card
Layout Types
Rich Media in Paywalls
Paywall sections support rich media content configured in the Console:- Lottie animations — animated hero images, feature illustrations, or backgrounds
- Rive animations — interactive state-machine-driven animations
- Video — inline video in header or feature sections
- Haptic feedback — triggered on plan selection and CTA taps
- Particle effects — confetti or celebration effects on purchase completion
- Per-section styling — background colors, gradients, images, borders, shadows, and corner radius
Promo Code Handling
When a paywall includes aPromo input section, implement the promo code delegate method to validate codes against your backend. The completion callback is a simple (Boolean) -> Unit — true accepts the code, false rejects it (the SDK renders the in-built rejection message):
Auto-Tracked Events
The paywall module automatically tracks the following events:Configuration in Console
Paywalls are created and managed in the AppDNA Console:- Navigate to Monetization > Paywalls.
- Create a new paywall or edit an existing one.
- Add sections (header, features, plans, CTA, social proof, guarantee).
- Link Google Play products to the plan options.
- Optionally assign the paywall to an experiment for A/B testing.
- Publish the paywall to make it available via the config bundle.
Full Example
The paywall module integrates with the billing module for purchase handling. See the Billing guide for the full purchase API.
Next Steps
- Set up Billing for detailed purchase handling
- Configure Onboarding flows that transition into paywalls
- Learn about Offline Support for paywall config caching

