Picture the scene: it's 7pm on a Friday night at a 10,000-capacity festival. There's a snaking queue of people stretching back across a field, all waiting to get in. Your gate staff are trying to scan tickets on phones. The venue WiFi just dropped out. Three of your scanners are spinning. The crowd is starting to get restless.
This is the moment you find out whether your ticket scanning app was designed properly.
We learned this lesson early when we built the REBLL ticket scanner — the mobile app that gate staff use to check tickets at venues across the UK. The app has to work in two modes that most people don't think about: offline at the gate, and online for syncing back to the platform. If you only solve the second mode, the gate becomes a customer-service nightmare the moment connectivity drops.
This post is about how we built it, why we chose Flutter, and the offline-first architecture that means a flaky venue WiFi never holds up the queue.
The choice: native vs cross-platform vs PWA
When we scoped REBLL's mobile scanner, we had three options:
- Two native apps — Swift for iOS, Kotlin for Android. Best performance, full access to platform APIs, but you maintain two codebases forever and need engineers in both ecosystems.
- Progressive Web App — single codebase, browser-based, installable. Worst camera and offline story; struggles with continuous barcode scanning under battery pressure.
- Flutter — single codebase compiled to native binaries on both platforms. Native performance for camera and rendering, full access to platform APIs through plugins, one team can maintain both apps.
We picked Flutter, and 18 months in we still think it was the right call. Here's why.
Cross-platform without the compromises
The standard objection to cross-platform mobile development is "you'll hit a wall the moment you need a native feature". With React Native this can be true. With Flutter we genuinely haven't. Camera access, barcode scanning, vibration feedback, audible beep on scan, filesystem access for the local SQLite cache, secure storage for credentials, network connectivity detection, background sync — all of it is available either through first-party packages or well-maintained community plugins.
The performance is also genuinely native. Flutter compiles your Dart code to ARM machine code on iOS and Android, and the rendering is done by Skia (now Impeller on iOS) at 60fps. For a scanner app where the user is rapidly pointing the camera at QR codes and expecting instant feedback, this matters.
One codebase, two app stores
The killer benefit, though, is operational. We have one Flutter codebase. We make a change, run flutter build apk --release and flutter build ipa --release, and we have updated builds for both Google Play and the Apple App Store. One bug fix lands on both platforms in the same release cycle. Every feature ships to both audiences simultaneously. Every test is written once.
For a small consultancy delivering a mobile app for a small client, this is the difference between "we can afford to maintain it" and "we can't justify the cost".
Why offline-first is non-negotiable
Now to the harder part of the architecture.
The venue connectivity problem
Festivals, nightclubs, music venues, conference halls — these are some of the worst environments for mobile internet you'll find anywhere in the UK. Reasons include:
- Crowded RF spectrum — 10,000 people all on their phones in one site saturate the local cell tower in seconds
- Building materials — steel-framed venues block signal, basement clubs sit below ground, marquees offer no help
- Venue WiFi — when it exists, it's usually consumer-grade equipment running on a single uplink that wasn't designed for hundreds of concurrent devices
- Power outages — even a 30-second WiFi router restart in the middle of a peak entry rush is enough to turn a smooth check-in into a queue from hell
- Roaming staff — gate staff move between entry points and the WiFi range from one access point doesn't always reach the next
If your scanner app requires a live API call to the server to validate every ticket, you're betting the success of the event on infrastructure you don't control. You will lose that bet, eventually, in the worst possible moment.
What "offline-first" actually means
Offline-first isn't a marketing label — it's a specific architectural pattern with real engineering consequences.
For the REBLL scanner, it means:
1. Pre-sync the entire ticket inventory before doors open. When gate staff arrive at the venue and connect to a working network, the app downloads the complete list of valid tickets for the event into a local SQLite database. Every ticket is identified by a unique scan code, and each record carries metadata: ticket type, holder name, status (unused, used, refunded, voided), and a sync version number.
2. Validate locally, sync later. When a punter presents their ticket at the gate, the scanner reads the QR code, looks up the ticket in the local SQLite cache, validates it, marks it as used locally, and immediately gives the gate staff a green light or red error. Zero network calls in the critical path. This typically completes in under 50 milliseconds, fast enough that the staff member is already moving the next punter forward before the previous scan finishes.
3. Queue scan events for later upload. Every successful scan is also written to a local "sync queue" table. Whenever the app detects working connectivity, a background worker uploads queued scans to the REBLL platform in batches. If the connection drops mid-upload, the queue stays intact and resumes when connectivity returns.
4. Detect duplicate scans across devices. Multiple gate staff often scan tickets in parallel. If staff member A scans a ticket on phone 1 while staff member B simultaneously tries to scan the same ticket on phone 2 (a common dodge attempt), the local cache on phone 2 hasn't yet learned about the scan from phone 1. We handle this with periodic background sync that pulls down updates from other devices, plus server-side reconciliation that catches any duplicates that slipped through. The honest answer is that detecting duplicates across offline devices is genuinely hard — but in practice the rate of attempted duplicate scans is low, and the server-side reconciliation catches them within seconds of connectivity returning.
5. Sync back the audit trail. Every scan event uploaded to the server includes timestamp, device ID, scan staff member, and ticket ID. This means the post-event audit trail is complete: every ticket can be traced to the exact device, time, and staff member who scanned it.
The local SQLite cache
The app uses SQLite via the sqflite Flutter plugin for local storage. Three tables matter:
tickets— the full ticket inventory pre-synced from the server, indexed by scan code for sub-millisecond lookupsscan_events— every scan that's happened locally, with status (pending sync, syncing, synced, failed)sync_state— metadata about the last sync time, the cursor position for incremental sync, and connectivity health
SQLite is genuinely the right tool here. It's embedded, has no daemon, supports indexes, transactions, and secondary key lookups, and can handle hundreds of thousands of rows on a phone without breaking a sweat. For a 10,000-capacity festival we typically pre-sync ~12,000 tickets (some buffer for last-minute releases) and the entire database is still well under 5MB. Lookups are sub-millisecond regardless.
Connectivity detection
Detecting "is the network actually working?" is harder than it sounds, because the OS will report "connected to WiFi" even when that WiFi has no upstream internet. We use a combination of:
connectivity_plusFlutter package for the basic "do we have any network?" signal- Periodic ping to a known endpoint (
/api/healthon the REBLL backend) to detect dead WiFi - Exponential backoff on failure so we don't drain the device battery hammering a broken endpoint
When real connectivity returns, the sync queue resumes automatically.
What we'd tell anyone building something similar
A few hard-won lessons.
1. Test offline mode like it's the primary mode
The temptation when building a scanner is to develop it on your laptop with full connectivity, then "add offline support later". This always produces a fragile system because the offline path is treated as a fallback, not the primary mode.
Build it offline-first from day one. During development, run the app in airplane mode for half your testing. If anything breaks in airplane mode, fix it before shipping. The online code path is the easy one — getting offline right is what makes the app actually reliable.
2. Pre-sync is a feature, not a chore
Some scanner apps try to be clever by lazy-loading tickets on demand. Don't. Pre-sync the entire event inventory before doors open, even if it takes 30 seconds. The 30 seconds happens once, in a controlled environment, with the staff still drinking their coffee. The alternative is hundreds of people waiting at the gate while the app tries to fetch one ticket at a time.
3. Audible + haptic feedback matters more than visuals
Gate staff are looking at the punter, not the screen. The scanner needs to tell them "valid" or "invalid" without them having to look down. We use:
- Short beep + 100ms vibration for success
- Different beep tone + longer vibration for invalid (already used, refunded, etc.)
- Three-pulse vibration + alert tone for hard failures (network gone, app error, etc.)
These cues let staff process scans at almost twice the speed compared to visual-only feedback. We covered the staff training in about 10 minutes and they were fluent within an hour.
4. Battery is the silent killer
A scanner running the camera continuously plus SQLite + sync work plus screen-on for 6+ hours will eat a phone battery. We optimised aggressively:
- Camera only powers up when the scan view is foregrounded
- The camera preview is set to the lowest resolution that still reliably reads QR codes
- Background sync is throttled when battery drops below 20%
- Screen brightness can be turned down by the user during the day
Even so, we recommend gate staff carry power banks. A dead phone in the middle of a queue is the only thing worse than a connectivity drop.
5. Make the deployment story trivial
REBLL's gate staff aren't tech specialists. They need to install the app on their personal phone, log in, get to work. We made this:
- Available on both Google Play and Apple App Store under the REBLL Tickets brand
- One-step login with a QR code shown in the REBLL admin dashboard — no usernames to type
- Auto-detects the event based on the device's permissions
- Pre-sync triggers automatically the moment they open the app at the venue
If installation and onboarding takes more than 60 seconds, you'll lose half your gate staff to "I couldn't get it to work" before you start.
The result
The REBLL scanner has now processed 50,000+ ticket scans across 2 large festivals and 10+ single-day events. We've had zero "the app died because the WiFi dropped" complaints. Several events have happened where the venue WiFi was effectively unusable for the duration of doors and the scanner just kept working from the local cache, syncing the audit trail back hours later when staff were tearing down.
Flutter let us deliver this with a single codebase, a single team, and a release cadence that any iOS-only or Android-only team would struggle to match. For the right kind of app — and a ticket scanner is exactly the right kind — it's a genuinely excellent tool.
When we'd recommend Flutter
For mobile apps where you need:
- Cross-platform from day one without compromising performance
- Camera, scanning, hardware integration with predictable native speed
- Offline-first architecture with local databases and background sync
- A small team that can't justify maintaining two native codebases
- Simultaneous iOS and Android releases for a unified user experience
For everything else — mostly content-driven apps, marketing apps, or anything that's basically a website wrapped in a webview — a PWA or even a mobile-responsive web app is usually the right call.
If you're building a mobile app and trying to figure out the right approach, get in touch. We'll have a proper conversation about your specific constraints rather than try to sell you Flutter as the answer to every question.
Further reading
- REBLL Tickets case study — the platform this scanner connects to
- Software Development for Northwest UK SMEs: What We See Working in 2026 — broader observations on what works for SMEs
- Mobile App Development service — how we work on mobile projects
Related reading
Software Development for Northwest UK SMEs: What We See Working in 2026
Practical observations from a Northwest UK software consultancy on what's actually moving the needle for SMEs in Manchester, Liverpool, Preston, and beyond — and what's still a waste of time.
Building a Self-Improving Document Classification Agent on Azure AI Foundry
A practical walkthrough of building an agentic AI system on Azure AI Foundry with persistent agents, human-in-the-loop approval, Cosmos DB vector memory, and a feedback loop that makes the agent smarter over time.