Common Orientation Change Bugs in Ticketing Apps: Causes and Fixes
Orientation change bugs happen when a ticketing app loses, duplicates, or corrupts state while moving between portrait and rotated modes. The issue is rarely “rotation” itself. It is usually state man
What causes orientation change bugs in ticketing apps
Orientation change bugs happen when a ticketing app loses, duplicates, or corrupts state while moving between portrait and rotated modes. The issue is rarely “rotation” itself. It is usually state management, layout recreation, or async work not surviving the activity or view lifecycle.
Common technical root causes
- Activity recreation: On Android, rotation destroys and recreates the activity unless handled carefully. Ticket details, selected seats, promo codes, and checkout state can reset.
- Fragment and navigation state loss: If selected tabs, bottom sheets, seat maps, or checkout steps are not restored, users return to the wrong screen state.
- Custom view redraw issues: Seat maps, barcode scanners, QR displays, countdown timers, and canvas-based price calculators often depend on exact dimensions. Rotation changes those dimensions and can break rendering.
- API calls firing twice: Network requests started before rotation may continue, restart, or complete on both old and new instances. This can duplicate cart items, ticket holds, or payment attempts.
- Payment provider callback mismatch: External payment SDKs may resume on a new activity instance with stale tokens or callbacks.
- Layout constraints not tested for rotated mode: Forms, confirmation buttons, legal checkboxes, and payment fields can overlap or become unreachable.
- WebView recreation: Ticket purchase pages, loyalty pages, or embedded event pages may lose cookies, session state, or scroll position.
- Time-sensitive ticket logic: Reservation timers, dynamic pricing, and inventory locks may continue in the background while the UI resets.
Ticketing apps are especially vulnerable because a purchase flow combines inventory, pricing, user identity, payment, and fulfillment in one session.
Real-world impact
Orientation bugs in ticketing apps are not cosmetic. They directly affect conversion and trust.
| Bug type | User impact | Business impact |
|---|---|---|
| Selected seats disappear | User must restart checkout | Abandoned purchase |
| Promo code resets | User pays more than expected | Support tickets and refunds |
| Payment button duplicates | User may be charged twice | Chargebacks and reputation damage |
| QR ticket fails to render | User cannot enter venue | Refunds and venue support load |
| Countdown timer resets | User loses reserved inventory | Confusion and checkout errors |
| Seat map becomes unusable | Accessibility and usability failures | Lost sales from mobile users |
Users usually describe these bugs in short, harsh reviews: “lost my seats,” “charged me twice,” “QR code disappeared,” or “app crashed while paying.” For ticketing apps, that translates into lower store ratings, more customer support volume, and revenue leakage at the most sensitive part of the funnel.
How orientation bugs show up in ticketing apps
1. Selected seats are lost after rotation
A user selects aisle seats, rotates the device, and the seat map reloads. Previously selected seats may appear available again, or the checkout button may remain disabled.
Why it happens: seat selection state is stored in a view instead of a lifecycle-aware model.
Fix: keep selected seats in a persistent checkout state object, not in the seat map view.
class CheckoutViewModel : ViewModel() {
val selectedSeats: MutableStateFlow<Set<SeatId>> = MutableStateFlow(emptySet())
fun toggleSeat(seat: SeatId) {
selectedSeats.update { current ->
if (seat in current) current - seat else current + seat
}
}
}
Also persist the state across process death:
viewModel.stateIn(scope, SharingStarted.WhileSubscribed(5000), CheckoutState())
or use SavedStateHandle for primitive checkout fields.
2. Ticket quantity or price resets
The user changes quantity from 2 to 4, rotates, and the app returns to 1 ticket. In dynamic pricing flows, this can also change the displayed total.
Why it happens: quantity is stored in an activity field or form component that resets on recreation.
Fix: store quantity, selected tier, fees, taxes, and total in a single checkout state. Recalculate totals from the same source of truth after rotation.
data class CheckoutState(
val ticketTypeId: String,
val quantity: Int,
val fees: Money,
val taxes: Money
)
Avoid storing only the final total. Store the inputs so the total can be recalculated safely.
3. Promo codes disappear or apply twice
A user enters a discount code, rotates, and the code is gone. Or worse, the app submits the same promo code twice and applies an invalid discount.
Why it happens: form fields are not restored, and promo validation calls are not idempotent.
Fix: save promo code state in ViewModel or SavedStateHandle, and make promo validation idempotent on the backend.
@Suppress("DEPRECATION")
savedStateHandle["promoCode"] = code
Prefer a checkout session model where promoCode, discountStatus, and discountError update together.
4. Checkout payment is duplicated
The user taps “Pay,” rotates during processing, and the app shows two payment spinners or submits two payment requests.
Why it happens: the payment action is tied to a button click and can be invoked again after the UI recreates.
Fix: use an idempotency key per payment attempt and disable repeat submission until the terminal state returns.
data class PaymentAttempt(
val idempotencyKey: String,
val status: PaymentStatus
)
On the backend, reject or safely ignore repeated requests with the same key. On the client, do not restart payment when the activity recreates.
5. QR ticket or barcode disappears after purchase
The user completes purchase, rotates, and the QR code is blank, cropped, or no longer scannable.
Why it happens: the QR is generated for the previous layout size, cached incorrectly, or rendered outside the new bounds.
Fix: regenerate the QR after layout changes and verify contrast, quiet zone, and minimum size.
LaunchedEffect(qrPayload, containerWidth) {
qrBitmap = QrGenerator.generate(
payload = qrPayload,
size = min(containerWidth, 480.dp.toPx().toInt())
)
}
Store the ticket token securely, but generate the visual code from the current view size.
6. Seat map becomes unreadable or inaccessible
After rotation, labels overlap, selected seats are hidden, zoom resets, or screen readers lose focus.
Why it happens: custom seat maps rely on fixed coordinates and do not recalculate hit targets or accessibility nodes.
Fix: recalculate seat positions from the available canvas size and expose accessibility metadata for each selectable seat.
fun buildSeatMap(width: Int, height: Int): SeatLayout {
return SeatPlacer.place(
seats = seats,
bounds = Rect(0, 0, width, height),
minTouchTarget = 48.dp.toPx().toInt()
)
}
Also restore zoom level and selected seat focus after rotation.
7. Countdown timer shows impossible values
The ticket hold timer says 09:59, the user rotates, and it resets to 10:00. Or it keeps running after the checkout screen is destroyed.
Why it happens: the timer is restarted from UI state instead of a server-backed expiration time.
Fix: store the server timestamp and expiration time. Render remaining time from that immutable value.
val remaining = max(0, expiresAtMillis - currentTimeMillis())
Do not trust a countdown value saved only in the UI.
How to detect orientation change bugs
Use both automated and exploratory testing. Manual rotation testing catches obvious layout issues, but automated tests are better for repeated checkout flows.
What to test
- Rotate during seat selection, quantity changes, promo code entry, payment processing, and ticket display.
- Force activity recreation, not just rotation.
- Check state restoration after process death.
- Compare screenshots before and after rotation.
- Inspect the accessibility tree after rotation.
- Monitor network calls for duplicate payment, promo, or seat-hold requests.
- Verify crash logs and ANR reports during rotation-heavy flows.
Useful tools and techniques
- Appium: automate Android orientation changes with `
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free