NFC uniform tracking iOS app for schools
TRAQ
A Flutter iOS app that tracks school uniforms with NFC labels: tap to register, issue and return garments, with Firestore transactions, per-school security rules and scripted TestFlight releases.
My role: Mobile and backend developer: Flutter app, Core NFC, Firestore, security rules and release automation
- Flutter
- iOS Core NFC
- Firebase
- Firestore
- TestFlight
Overview
TRAQ lets teachers track school uniforms by tapping an iPhone on a small NFC label sewn into each garment. The app reads the label's unique ID through Core NFC and drives a simple register, issue and return workflow on Cloud Firestore, with each school's data isolated by security rules. It was built as a production-ready iOS prototype on the client's Apple Developer account, verified on a physical iPhone with real NTAG213 labels and shipped through TestFlight.
The challenge
Schools lend sports kits and uniforms to students and lose track of them: paper sign-out sheets go missing, nobody knows which jersey is with whom, and items are never returned. Teachers need something faster than a spreadsheet that works in a changing room.
TRAQ had to make tracking as quick as a tap: identify a garment instantly from an NFC label, record who has it, and show what is still out, with each school's data kept separate and the app installable on teachers' iPhones through TestFlight.
How it works
- 1Tap iPhone on label
- 2Core NFC reads UID
- 3Register / issue / return
- 4Firestore transaction
- 5Rules check the school
- 6Live counts update
Architecture
The Flutter app reads NTAG213 labels with Core NFC through nfc_manager, polling ISO 14443 and taking the UID from the MiFare identifier, with an ISO 7816 fallback. The required entitlements and Info.plist identifiers are set so iOS will start the reader session. Each scan is wrapped in a Completer that maps reader errors to cancelled or failed results and clears any stale session first, working around a plugin defect where invalidated sessions were never released.
Data lives in Cloud Firestore under organisations/{orgId}, with garments keyed by tag UID, students with normalised case-insensitive IDs, and loans; a top-level users collection holds each teacher's school. Security rules look up the caller's school for every read and write, validate the school ID and join code when a teacher signs up, and make user profiles immutable from the client. Issue and return run as Firestore transactions, and the UI is driven by snapshot streams, with the on-loan list joining garments to open loans on the client to avoid composite indexes.
Releases are fully scripted: xcodebuild archives and exports with automatic provisioning, and the App Store Connect API, signed with an ES256 JWT, registers the bundle ID, enables NFC Tag Reading, registers devices, creates internal beta groups and testers, and expires superseded builds. Build-time flags select milestone features from one codebase.
What I built
Core NFC tag reading
NFCTagReaderSession via nfc_manager with ISO 14443 polling reads each label's UID (MiFare identifier with ISO 7816 fallback) without writing to the tag; also found and worked around a plugin defect that left invalidated sessions open.
Firestore per school
Organisations with garments (tag UID as document ID), students and loans; every read and write is scoped to the caller's school by security rules, including a join-code check for teacher self sign-up.
Transactional issue and return
Issue creates the student if needed, opens the loan and flips the garment status in one transaction; return closes the loan and frees the garment atomically, so a garment never has two open loans.
Live, reactive screens
Snapshot streams for garments, items on loan and recent activity keep the home screen counts and lists current the moment anything changes.
Scripted iOS releases
xcodebuild archive and export plus the App Store Connect API (ES256 JWT) register the bundle ID, enable NFC, register devices, manage TestFlight testers and expire old builds from the command line.
Staged milestones from one codebase
Build-time feature flags via --dart-define produce milestone-only builds without removing code; Material 3 theme from the client's logo colours, scan animation and haptics.
Engineering decisions
- Use the label's factory UID as the garment ID and never write to tags, so labels can't be altered or cloned by the app.
- Enforce school isolation in Firestore security rules, not just in the app, so a modified client can't read another school's data.
- Run issue and return as transactions, so the garment status and its loan always agree.
- Script signing and TestFlight through the App Store Connect API, so releases are repeatable without clicking through Xcode.
Results
- Verified on a physical iPhone with real NTAG213 labels and shipped through TestFlight.
- Issue and return are atomic, with exactly one open loan per garment.
- Each school's data is isolated by Firestore security rules.
- Signing, provisioning and TestFlight uploads run from the command line.
Stack
- App
- Flutter, Dart, Material 3, Impeller
- NFC
- iOS Core NFC (NFCTagReaderSession), nfc_manager, NTAG213 / ISO 14443
- Backend
- Firebase Authentication, Cloud Firestore (security rules, transactions, snapshot streams)
- iOS build
- Xcode, Swift Package Manager, entitlements, xcodebuild, devicectl
- Release
- TestFlight, App Store Connect API (JWT, ES256), Python (requests, PyJWT)
- Tools
- Git, GitHub, Pillow, ffmpeg
Need something like this?
I can build a version of this for your product, your data and your stack.
Start a project