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.
Ready-made picker behavior
The drop-in picker observes application lifecycle automatically. When it returns to the foreground it:
- reports foreground lifecycle to SeatLayer;
- applies any hold-lapse or lost-inventory state already available;
- optionally asks for a fresh availability read; and
- 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:
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
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:
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.
@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.