# Flutter lifecycle and recovery — 2026-11-01

## Release boundary

Flutter `mysuperwall_sdk` **1.4.0** includes the lifecycle, recovery and motion
changes described here. Version 1.3.1 lacks the new APIs. iOS 1.6.0 is published;
React Native 1.6.0 is published and verified; Android 1.6.0 is completing Central
publication. See the current status at the end of this document. Inspect installed
source and lockfiles; registry publication does not imply device verification.

## Correct billing outcomes (existing APIs)

Use the actual store/provider result, not translated text or a generic bool.

| Provider outcome | SDK report |
| --- | --- |
| Confirmed purchase/entitlement | `trackPurchaseSuccess(productId: id)` |
| User cancelled checkout | `trackPurchaseCancelled(productId: id)` |
| Billing error | `trackPurchaseFailed(productId: id, errorMessage: safeCode)` |
| Pending approval/payment | No terminal success/cancellation/failure report yet |

A button tap already records purchase_started. Keep the billing spinner and
repeat-tap guard around the awaited operation, clearing both in finally. Update
`setSubscriptionStatus` from real entitlements. Do not fabricate a USD amount from
a localized non-USD store price.

Follow-ups reuse billing callbacks. Do not capture and force the first screen's
paywall ID/trigger key for later purchases: omit optional overrides so the SDK uses
its recorded product-button context. Restore success uses the current displayed
paywall ID; never pass an old source ID after navigation to another paywall.

## Lifecycle in Flutter 1.4.0+

These snippets require Flutter 1.4.0+ and must not be pasted into 1.3.1.

```dart
// Existing application service/controller handlers, registered once.
client.setPresentationHandlers(
  onPurchase: purchaseThroughExistingBilling,
  onRestore: restoreThroughExistingBilling,
);

// Preserve the existing MaterialApp/GetMaterialApp builder.
MySuperWallHost(
  lifecycleReady: onboardingComplete,
  child: existingBuilderResult,
)
```

No billing callbacks are required on the host. Explicit app-level handlers have
priority. A handler inferred from a manual call is usable only while its context
is mounted; do not rely on a short-lived screen to configure startup automation.

| Key | Timing |
| --- | --- |
| `mysuperwall.install` | First SDK use on this device |
| `mysuperwall.update` | First launch after version change |
| `mysuperwall.session` | SDK initialization or return after 30 minutes in background |
| `mysuperwall.foreground` | Return from background |
| `mysuperwall.session_N` | Reach N total sessions, once |
| `mysuperwall.day_N` | N elapsed days since first SDK use, once |
| `mysuperwall.active_minutes_N` | N total active minutes excluding background, once |

Use integer thresholds from 1 to 100,000. This is elapsed use, not a timer that
opens UI while the app is closed. One-time events remain eligible for 30 days from
occurrence; unknown history before collection is not reconstructed. History is
local to the installation. Retention is not an exactly-once transactional guarantee
across crashes. The published package's fixed keys still require an explicit
`handleLifecycleTriggers(context, onPurchase: ..., onRestore: ...)` call after UI
readiness. Do not claim these newer semantics on the published package.

## Follow-up policy and troubleshooting

Save and publish the source paywall, activate the target trigger and its published
paywall, and use the correct app key/platform. iOS and Android app records are
separate. Check actual billing event types in Debugger before changing rules.

The built-in runner handles delay, once-per-session, cooldown, maximum chain depth,
skip-if-premium and repeat prevention. Cooldown is a minimum between successful
attempts, not an additional delay on the first attempt. Actual server selection
controls repeat prevention across audience/experiment routing. The updated source
also binds prepared presentations, forwards purchase cancellations and does not
consume usage merely because a timer was scheduled and then cancelled/failed.

`setAutomationActionHandler` replaces the default runner. Use it only for an
intentional custom implementation preserving every policy, billing callback,
identity/foreground check and server decision. Do not install a delay-only runner.

Lifecycle Debugger states are received, pending, matched, shown, rejected and
expired. Matched means server authorization, not screen visibility. Shown is an
SDK display callback, not independent proof of a store purchase.

No Flutter build, app run or device purchase test was performed for these changes.
Report installation source/version and remaining runtime verification accurately.

## Deferred follow-up offers — updated local Flutter source

In the paywall editor's Follow-up tab, each event can use `immediate`, `elapsed`
(minutes since that event), `active` (additional foreground-use minutes),
`next_session`, or `next_foreground`. Minute scheduling is relative to the source
paywall event, not installation. `waitMinutes` accepts 1–43,200. `minSessions`,
`minDays` and `excludeFirstSession` are additional eligibility conditions.

`expiresMinutes` defaults to 1,440 and must exceed the waiting time for minute
modes. `maxShowsPerDay` defaults to 1 for this source/event/target rule, counted by
UTC day; once-per-session, cooldown and chain/repeat protection still apply.
`cancelOnPurchase` defaults to true, cancelling the queued offer on a confirmed
purchase/restore or a premium entitlement update. Initial rule values are not
changed just because the editor is opened or a legacy rule is re-saved.

Pending offers and per-rule usage are stored locally per app/user. A repeated
event does not push an existing offer's clock forward. Offers are evaluated while
the app is resumed and ready, normally within a 15-second foreground polling
interval after eligibility. No operating-system/background alarm or push is
created. On restart, elapsed-time offers use the stored timestamp; active-use
offers resume from persisted active seconds. Missing billing handlers defer
presentation. Register application-level handlers for reliable cold-start use.
The next delivery request still obtains current server authorization. Unpublished,
disabled or changed source policies invalidate their pending offers.

The new SDK advertises `deferred_follow_up_v1`. Backend delivery explicitly
disables advanced timing rules for clients that do not advertise this capability,
so old SDKs cannot accidentally show a ten-minute offer immediately. These changes
are included in Flutter 1.4.0. Other SDKs do not gain this feature
from a dashboard change. Existing custom automation handlers must implement the
new policy fields themselves; installing a custom handler opts out of the default
persistent runner. This is local scheduling, not cross-device or transactional
exactly-once delivery. Do not claim device verification from static checks.

## Native presentation animation

Custom slide styles use the configured duration (0–4,000 ms) and curve on entry
and exit in the updated Flutter source. System mode uses platform/SDK defaults;
None has no transition, so the editor disables duration/curve for those modes.
Reduced-motion settings suppress SDK transitions. The updated prepared path
reverses its animation before detaching; programmatic route dismissal uses pop
and waits for the reverse transition before permitting another presentation.
Kapatılabilir / dismissable controls user back/edge gestures; the paywall's own
close button and explicit application dismiss calls can still close it. The
prepared back-interceptor respects the same policy. Dismiss analytics are emitted
once per route close in the SDK wrapper. These SDK corrections remain local and
unpublished; code analysis is not device animation verification.

## Additional motion styles (Flutter 1.4.0+)

Custom styles now share one `PaywallMotion` implementation across prepared and
route presentation; the WebView is retained as a stable child. No blur, screenshots,
HTML animation injection or repeated view creation is used for SDK transitions.

| Editor style | Wire value | Recommended entry/exit duration |
| --- | --- | --- |
| Soft Rise | `fade_slide_up` | 340 ms |
| Soft Fade | `fade` | 220 ms |
| Scale In | `scale_up` | 320 ms |
| Scale Settle | `scale_down` | 300 ms |
| Slide From Left | `from_left` | 300 ms |

New choices select the `smooth` cubic curve (0.22, 1, 0.36, 1) and the recommended
duration. Existing paywall configurations are not automatically changed. An
additional `easeInOutCubic` curve is available for balanced motion. Scale changes
are deliberately small (0.96→1 and 1.035→1); Soft Rise moves 24 logical pixels and
fades. Opacity is clamped even for overshooting legacy curves. Existing slide
aliases remain backward compatible. Unknown effects fall back to System in older
SDKs; they do not implement the new effects. System still uses platform/SDK defaults,
None has no animation, and reduced-motion preferences disable SDK transitions.
Native-device smoothness and frame timing have not been verified in this change.


## Cross-platform updated-source setup (unreleased)

Cross-platform source/API contract (2026-11-01; see current release status): the new lifecycle history, 30-day pending signals, deferred_follow_up_v1 minute/session/foreground offers, cancellation corrections and expanded/instant motion have now also been implemented in the local Android, iOS and React Native source checkouts. Flutter 1.4.0 and iOS 1.6.0 are published; RN 1.6.0 is published and verified; Android 1.6.0 Maven publication remains pending. Do not infer support from unchanged version metadata, a source ZIP, an old tag or Flutter support. Before writing integration code, inspect the resolved installed SDK source, lockfile and available API signatures. If these features are requested but missing, report the SDK update as required; never invent a version, silently replace the dependency or claim installation is complete. Updated Flutter uses MySuperWallHost, setPresentationHandlers and lifecycleReady. Updated iOS uses MySuperwall.shared.setLifecycleHost(presenter,onPurchase:onRestore:) and setLifecycleReady(_:); register on the main thread with a live UIKit presenter (preserve the existing SwiftUI bridge). Updated Android uses MySuperwall.get().setLifecycleHost(activity,onPurchase,onRestore) and setLifecycleReady(Boolean) with the existing FragmentActivity. Updated React Native uses MySuperwall.shared.setPresentationHandlers({onPurchase,onRestore}), setLifecycleReady(boolean), and the existing usePaywall/PaywallModal mounted once at app root; provide the real native appVersion to initialize for app-update signals. Never copy Flutter API names or billing report methods onto another platform: inspect that platform's trackPaywallEvent and callback signatures. Keep application billing handlers durable, do not capture stale source paywall IDs, and do not register per-screen dismiss callbacks as global handlers. Defer automatic presentation through onboarding/login, then mark ready. Automatic offers run in foreground with valid billing handlers, not through OS background alarms. Use the built-in scheduler; do not reimplement minute offers with a naive timer. Preserve expiry, cooldown, once-per-session, daily caps, premium, repeat and chain safeguards and cancel pending offers only on confirmed purchase/restore/entitlement. Motion none means instant entry and exit; do not confuse it with disabling the template close button. Keep the host mounted through closing transitions; reduced motion must be respected. Missing lifecycle triggers are expected pending diagnostics, not app errors. Always report the installed source/version, unresolved billing wiring, and whether device validation actually occurred. Static checks do not prove real-device purchase, restore, delayed delivery or frame pacing.


## Current publication status

SDK release status — 2026-11-01: Flutter mysuperwall_sdk 1.4.0 is published on pub.dev and includes automatic lifecycle, lifecycleReady, setPresentationHandlers, 30-day history, deferred follow-up offers and motion fixes. iOS SPM 1.6.0 is published at github.com/Unevenn/mysuperwall-ios. React Native @mysuperwall/react-native 1.6.0 is published and its downloadable package is verified. Android 1.6.0 source and signed bundle are ready but Maven Central publication is PENDING; keep the currently verified 1.5.1 dependency unless the owner explicitly selects the updated source. Do not use new APIs on older resolved packages. Inspect installed source and lockfiles. For Flutter keep MySuperWallHost, register existing app billing through setPresentationHandlers before first automatic display and defer onboarding with lifecycleReady=false. Native updated sources use setLifecycleHost and setLifecycleReady; React Native uses setPresentationHandlers, setLifecycleReady and the existing root PaywallModal, with real initialize({appVersion}). Do not call Flutter APIs on native/RN. Sessions begin at SDK initialization or after 30 background minutes; days mean first SDK use; active time excludes background. Signals last 30 days; offers require foreground/readiness and built-in safeguards. Never infer successful purchase from a button tap, pending result or bool false. Device billing and frame pacing remain unverified.
