VECTIQ
← All posts
Engineering note · Platforms & Software

Attendance in a Basement Classroom

The feature that decides whether a school app gets used is not the dashboard. It is whether a teacher can mark attendance in a room with no signal and trust that it arrived.

by Vectiq Engineering · 2026-09-17 · 6 min read
Attendance in a Basement Classroom

Every school management app demo happens in an office with good wifi. Every school management app fails for the first time in a basement classroom five minutes before the bell.

We learned this the ordinary way, and ECMS Mobile is what came out of it.

The failure that matters

A teacher opens the app, taps through thirty students, presses save, and gets a spinner. The bell goes. They close the app.

What happens next decides whether the school keeps using the system. If the marks are gone, the teacher writes on paper for the rest of the term and the platform has lost. If the marks are queued and arrive when the phone finds signal in the corridor, nobody even notices there was a problem.

So attendance is not written to the network. It is written locally, and an outbox drains it when a connection exists.

What "offline-first" has to mean in practice

The word is used loosely. Three things have to be true or it is decoration:

  • The write succeeds locally and the UI says so. No optimistic spinner that might still fail. The mark is recorded; sending it is a separate concern the user does not have to think about.
  • The outbox is durable and idempotent. It survives the app being killed, and re-sending the same mark twice does not create two records. The server must treat the second attempt as the same event, not as a correction.
  • Reads work too. A class roster the teacher cannot open is as useless as a save that fails. Class data is cached so the screen renders without a round trip.

Generated clients, so a contract change breaks the build

The app covers fifteen feature areas across roughly 367 Dart files: AI, assignments, attendance, auth, classes, contents, exam, family, finance, home, LMS, notifications, profile, schedule and SRS. That is a large surface to keep in sync with a NestJS API that is still moving.

Six of those areas now use API clients generated from the platform's OpenAPI specification rather than hand-written HTTP calls. The difference is where a mistake shows up: a renamed field used to surface as a blank screen for a teacher in week three, and now it surfaces as a compile error on the developer's machine in minute one.

NOTE
Generated clients are not free either

The spec becomes load-bearing. If the backend ships a route that the spec does not describe, the app cannot call it, and "just add it by hand for now" quietly reintroduces the drift you generated clients to avoid. The rule that keeps it honest: the spec is part of the API's definition of done, not a document produced afterwards.

Error classification, because "something went wrong" is not a message

The network core sorts failures into categories the UI can actually respond to: no connectivity (queue it, say so calmly), authentication expired (refresh, retry once, then send the user to login), a validation rejection (show the field), and a server fault (report it, keep the local copy).

Collapsing all four into one red toast is what produces the support ticket that says "the app doesn't work".

The boring conclusion

None of this is a feature anyone asks for in a procurement document. Nobody writes "must have a durable outbox" in a tender.

But the platform is judged on whether a teacher with a phone and one bar of signal can finish a task in the ninety seconds before class starts. Everything else - the analytics, the AI tutor, the revenue forecast - is only reached by people who got past that first test.

FlutterOffline-firstOpenAPIECMSMobile