FullBag transport app
A React Native driver app that shares a school vehicle's live location with school admins and parents, and records student attendance by QR code.
Built alone, start to finish
- Role
- Sole developer. I designed and built the whole app.
- Timeline
- July 2026 – August 2026
- Platform
- Android app for drivers, part of FullBagERP. Private, not on public app stores.
- Stack
- React Native, Expo, TypeScript, Expo Router, Zustand, SQLite, Leaflet with OpenStreetMap
The problem
Schools in Nepal run their own buses and vans. School admins need to know where every vehicle is, and parents want to know where their child's bus is without phoning the driver.
The driver app shares the vehicle's position with FullBagERP while the driver is on a route, and records which students are on board.
What it does
Sign in with a school code
One app serves every school. The driver enters their school's code once, and it's stored in the phone's keychain.
My buses
Drivers only see the buses they are rostered on in the school's transport timetable.
Drive
A map and a Start/Stop button. While driving, the app records the position every 30 seconds and keeps the screen awake.
QR code attendance
Student attendance on the vehicle is recorded by QR code, so the school and parents know who is on board.
Live view in FullBagERP
School admins see every vehicle. Parents see only the bus their own child is assigned to.
My part
- I built the whole app on my own: the screens, sign-in and session handling, the location tracker, the offline queue, the map, the QR attendance, and the Android release build with EAS.
- On the ERP side I built the live tracking endpoints the app reports to, and the parents' view of their child's bus.
- Add FullBag ERP Driver App
- feat: multi-school login, offline location queue, keep screen awake
- build: add EAS build config and signing setup
How it works
Every 30 seconds the app reads a high-accuracy GPS fix and writes it to a SQLite table on the phone before trying to send anything.
It then uploads waiting positions to the FullBagERP API in batches of up to 200, grouped by bus, with the school code in an X-Tenant-ID header and the driver's token from the phone's keychain.
The API checks that the driver is rostered on that bus and has permission to report its location, then stores the position in that school's database. The admin map and the parents' map read from there.
Hard problems and decisions
No position lost when the signal drops
- Problem
- Mobile signal drops on the road. If the app only sent positions live, every dead zone would leave a gap in the route.
- What I chose
- Every fix goes into an on-device SQLite outbox first. The uploader sends batches in order and stops at the first failure, leaving the rest queued for the next try. Each position carries a client ID, and a unique index on bus and client ID means a retried batch is never stored twice.
- Why
- The phone keeps every position until the server has it. A long outage is capped at the newest 5,000 positions, more than a full day of driving.
One app for every school
- Problem
- FullBagERP gives each school its own database, and building a separate app per school would not scale.
- What I chose
- The driver enters a school code at sign-in. It is stored in the device keychain and sent as X-Tenant-ID on every request, the same header the web app uses.
- Why
- One build works for every school, and the server routes each request to the right school's database.
Positions never sent under the wrong driver
- Problem
- If one driver signs out and another signs in on the same phone, positions still queued from the first must not upload under the second driver's account.
- What I chose
- Signing out clears the outbox. The server only accepts positions for buses the driver is rostered on, with a dedicated permission for reporting location.
- Why
- Each position is tied to the person who was actually driving.
A stopped tracker that stays stopped
- Problem
- Stopping and starting a drive quickly could leave an old timer still running, posting positions for the previous session.
- What I chose
- Every start bumps a generation number. Each tick checks it before reading GPS and again before reporting, and quits if a newer run has started.
- Why
- Only one tracking loop can ever be live, without needing locks.