Skip to content

Architecture

How the Flutter picker is layered: native widgets, the scope and controller, one picker snapshot, and the drawn SeatLayer venue map.

Updated View as Markdown

The Flutter picker is one native widget tree over one drawn map, joined by a single seam. Understanding that seam is what makes the customisation and composition guides obvious rather than magical.

The layers

The Flutter picker stack: the ready-made SeatLayerPicker and your own composition both sit on SeatLayerPickerScope, which owns the controller and the picker snapshot; those drive the SeatLayer venue map, which reaches the SeatLayer API.

Layer What lives there Who owns it
Your app Routes, navigation, checkout, analytics You
Picker widgets Header, legend, dock, confirm card, cart, 3D chrome Flutter, yours to change
Scope and controller SeatLayerPickerScope, SeatLayerPickerController, SeatLayerPickerState The SDK
Venue map The drawn venue, pan, pinch, 3D scene SeatLayer
API Live inventory, holds, best available SeatLayer

Two rules bind the whole design:

  1. The venue map owns the venue; native owns the chrome. The map knows Flutter owns the furniture, so it draws no seat tooltip, test-mode badge, or attribution of its own. Exactly one of each is drawn in Flutter.
  2. A row name may already contain its section. The SDK prints row labels with the section prefix removed, so a card reads Stalls D · Row C rather than Stalls D · Row Stalls D C.

The snapshot is the seam

State reaches Flutter as a complete replacement — never a patch. Dart ignores stale state and serialises inventory-changing actions, so a repeated checkout tap cannot produce two holds.

Snapshot field What it carries
event key, name, venue, mode, currency, startsAt, salesClosed
branding brandName, logoUrl, attributionRequired, organizer palette
categories key, label, color, priceMin, priceMax, available, tiers
sections id, label, displayLabel, zoneId, color, dominantCategoryKey, seatsLeft, priceMin, priceMax
zones id, label, color — plus bestAvailableZones
map rung, focusedSectionId, focusedSection, viewMode, buyerView, floors, activeFloorId, floorMode, categoryFilter, accessibilityFilter, colorblindSafe, canZoomIn, canZoomOut, viewportInsets
selection SelectedSeat per seat: label, section, row, seat number, price, tiers
cartLines SeatLayerCheckoutLineItem per line, with cartTotal and ticketCount
hold active, expiresAt, owner — never the hold id
capabilities Which optional features are available on this event
accessStatus Whether buyer access is configured, and why not

The map snapshot’s rung is the vocabulary the whole picker navigates by: venue is the overview, and seats means a section is focused and its seats are revealed.

Commands go the other way

SeatLayerPickerController is a ValueNotifier<SeatLayerPickerState> and the only thing that drives the map. Its surface is typed Dart, not raw strings:

Group Methods
Navigation focusSection, overview, setRung, setFloor, showAllFloors
Selection selectObjects, deselectObjects, selectCategories, deselectCategories, removeObject, clearSelection, setSelectableObjects, setMaxSelection, setSeatTier
Filters setCategoryFilter, setAccessibilityFilter, setLimitedViewHidden, setColorblindSafe
View setViewMode, setBuyerView, showSeatIn3D, openSeatView, set3DNavigationMode, zoomIn, zoomOut, zoomToFit
Inventory bestAvailable, setGeneralAdmissionQuantity, setTableQuantity, resumeHold, extendHold
Handoff checkout, rejectCheckoutHandoff, releasePickerOwnedHold
Surface setViewportInsets, setMapInteractionEnabled, setCartSheetExpanded, setThemeMode
Lifecycle synchronize, retry, setLifecycle, close, destroy, dispose

Read state without owning a controller through the scope’s static lookups — SeatLayerPickerScope.stateOf(context), SeatLayerPickerScope.controllerOf(context), SeatLayerPickerScope.brightnessOf(context), and SeatLayerPickerScope.stringsOf(context) among them. See building your own layout for how a custom widget uses them.

From a tap to a handoff

Sequence across four lanes — buyer, native chrome, drawn map, your server: the buyer taps a seat, the map raises a selection event, a snapshot arrives and the native confirm card appears while the map is made inert, the buyer confirms, select and hold commands go out, the hold comes back inside the next snapshot, and checkout returns a typed handoff to your callback.

Two details in that flow are load-bearing.

The map is made inert, not just ignored. While native decision chrome is up, the picker makes the map itself inert, and a Flutter IgnorePointer sits over it as a second layer. Both are required: the map surface can be hit-tested beneath composited Flutter chrome even when the Flutter child ignores pointers, so the originating tap would otherwise select a second seat underneath. As soon as the prompt closes, the map owns pan and pinch again.

Native chrome tells the map where it can draw. setViewportInsets reports how much of the map surface your chrome covers, so the map frames a focused section, the venue overview, and a best-available result into the part the buyer can actually see. It is a framing inset, not a clip — the venue still draws and pans underneath. The drop-in reports its own rail and dock automatically; a custom layout reports its own.

The back-navigation ladder

The back ladder: an expanded cart sheet collapses to its peek, an open confirm card dismisses, a focused section returns to the overview, and only at the overview is your route allowed to pop.

One PopScope owns all four rungs, so Android predictive back and the iOS edge swipe take the same path a buyer expects — and a hold already handed to your app is never released by a close, because ownership moved with the handoff.

The picker fails closed

Each SDK release is pinned to a matching SeatLayer renderer version, so an app on a given seatlayer version always gets the same renderer. Where a feature is unavailable — an optional one you turned off, or one an older renderer predates — the control that drives it is absent rather than decorative. The picker never shows a button that silently does nothing.

That gating is what makes new features additive. SeatLayerFloorStrip draws nothing on a venue with fewer than two floors; SeatLayerSeatViewChrome prints the seat-view caption natively only where the map has handed those words over, and otherwise leaves the map to draw them. An app pinned to an older release behaves exactly as it did before.

Where credentials live

Buyer access is negotiated at the SeatLayer renderer’s own origin, and any token stays in memory. It never appears in a URL, in a snapshot, or in an application event, and the picker’s own state model has no field that could carry one. Your app never holds a SeatLayer secret; for gated inventory it supplies a callback that asks your backend to mint a short-lived, origin-bound buyer session.

Next steps

Navigation

Type to search…

↑↓ navigate↵ selectEsc close