---
title: "Lifecycle and recovery"
description: "Keep the SeatLayer Flutter 0.7.2 picker current across routes, backgrounding, hold lapse, unavailable seats, and checkout ownership changes."
---

Mobile time is discontinuous. A buyer can background the app, open checkout on
another route, lock the device, or return after another buyer has purchased the
same seats. SeatLayer Flutter `0.7.2` reconciles those moments with server truth
instead of trusting an in-app timer that may not have run.

<Aside type="note" title="Applies to seatlayer 0.7.2">
  Foreground availability refresh, hold-lapse recovery,
  `SeatLayerPicker.routeObserver`, and
  `SeatLayerPickerController.refreshAvailability()` are included in `0.7.2`.
</Aside>

## Ready-made picker behavior

The drop-in picker observes application lifecycle automatically. When it returns
to the foreground it:

1. reports foreground lifecycle to SeatLayer;
2. applies any hold-lapse or lost-inventory state already available;
3. optionally asks for a fresh availability read; and
4. reconciles authoritative picker state before accepting another buyer action.

Selection, camera, floor, focused section, and cart presentation remain in place
unless authoritative inventory makes a selected object unavailable.

## Observe routes as well as app lifecycle

Register the SDK route observer when another Flutter route can cover the picker:

```dart
MaterialApp(
  navigatorObservers: [SeatLayerPicker.routeObserver],
  home: const App(),
);
```

Without it, background/foreground reconciliation still works. The observer adds
the route-return half: a picker covered by an in-app checkout or details route
can refresh when that route pops.

## Control automatic behavior

```dart
SeatLayerPicker(
  configuration: configuration,
  options: const SeatLayerPickerOptions(
    refreshOnResume: true,
    announceHoldLapse: true,
  ),
  callbacks: SeatLayerPickerCallbacks(
    onHoldExpired: returnBuyerToSelection,
    onSelectedObjectUnavailable: explainLostInventory,
  ),
  onCheckout: (handoff) async {
    await checkoutBackend.begin(holdId: handoff.holdId);
  },
)
```

`refreshOnResume: false` suppresses the SDK's optional explicit availability
request. It does **not** discard a lifecycle outcome the SDK already
reported; ignoring that result could leave the cart showing inventory the
server has released.

`announceHoldLapse: false` disables the built-in visible lapse message while
state and callbacks still update. Use it when your host provides an equivalent,
accessible explanation.

## What happens when a hold lapses

When an owned hold is gone, the picker stops its countdown immediately, clears
the hold state, calls `onHoldExpired`, and announces the outcome once. Seats
that remain available can be offered back through the built-in
`SeatLayerHoldLapseNotice`; selecting the recovery action reselects and holds
them again.

The offer is not a promise. A seat can sell between the availability read and
the buyer's recovery tap. That race returns a typed failure and leaves the UI in
a truthful unheld state.

## Unavailable selected objects

A refresh can discover that another buyer purchased a selected seat while this
app was away. The picker deselects that object and reports it through
`onSelectedObjectUnavailable`. Do not silently put it back into the cart or
calculate payment from the stale local selection.

## Custom layout responsibility

`SeatLayerPickerScope` retains the picker state and lifecycle machinery. A
custom component can request a read explicitly:

```dart
final picker = SeatLayerPickerScope.controllerOf(context);
final result = await picker.refreshAvailability();
```

Use the typed result and current `SeatLayerPickerState`; do not compare local
timers. If a completely headless host takes over
lifecycle control, it must serialize refresh/synchronize with other
inventory-changing actions.

## Hold ownership across navigation

Before checkout, a picker-owned hold is released during orderly picker close.
After `SeatLayerCheckoutHandoff` crosses into the host, ownership has moved to
your app, so dismissing the picker does not release it.

If `onCheckout` throws because server validation or navigation failed, the
ready picker rejects that exact handoff and returns to a recoverable state. Do
not catch the error and pretend checkout opened.

Persist an opaque `holdId` only when the product promises checkout restoration.
On a new picker session, use `SeatLayerPickerOptions(initialHoldId: ...)`; that
restored hold is host-owned and picker cart controls do not release it.

## Optional picker prewarm

`SeatLayerPicker.prewarm()` prepares the picker before navigation. It
does not contain an event, credential, selection, or hold, so it does not create
a hidden buyer session.

```dart
@override
void initState() {
  super.initState();
  SeatLayerPicker.prewarm();
}

@override
void dispose() {
  SeatLayerPicker.cancelPrewarm();
  super.dispose();
}
```

Cancel an unused prewarm when the source screen no longer leads to the picker.

## Verification matrix

| Scenario | Expected outcome |
| --- | --- |
| App backgrounds with selection only | Selection stays unless refreshed inventory invalidates it |
| App backgrounds with an open hold | Server hold state and expiry replace the paused local timer |
| Checkout route covers picker | Route return triggers reconciliation when the observer is registered |
| Held seat expires while away | Countdown stops, expiry callback fires, buyer receives one recovery explanation |
| Selected seat sells while away | Seat is deselected and reported as unavailable |
| Host checkout callback throws | Handoff is rejected and picker remains recoverable |
| Host-owned restored hold closes picker | Picker does not release the host's hold |

Test these paths on physical iOS and Android devices with a live test event.

## Related pages

- [Flutter quick start](/buyer-sdk/flutter)
- [Architecture](/buyer-sdk/flutter/architecture)
- [Custom layout](/buyer-sdk/flutter/custom-layout)
- [Holds and checkout](/buyer-sdk/holds-and-checkout)
- [Shared troubleshooting matrix](/buyer-sdk/mobile#troubleshooting)
- [Tagged picker controller source](https://github.com/seatlayer/seatlayer-flutter/blob/v0.7.2/lib/src/picker/seat_layer_picker_controller.dart)