Civiq app architecture
How the Flutter app is put together: the paths a request takes, the flows behind each feature, and the reasoning behind the design. Everything here was derived directly from the current source in lib/.
Civiq lets citizens look up their elected representatives — local through national — from GPS or a searched location, see office locations on a map with district boundaries, contact representatives through official channels, and discuss civic matters in district-scoped community hubs. The app talks to a single custom Go backend; every request to it is gated by hardware-backed device attestation, with user accounts layered on top for the features that need identity.
Three decisions shape almost everything else in this codebase. First, the server is the only data authority, but the app is built to feel local: congressional data is synced into SQLite behind SHA comparisons, so the common path renders entirely from disk and the network is consulted only to ask "has anything changed?". Second, no request reaches the server anonymously — an attestation ceremony runs before the first frame of real UI, and its headers ride on every call. Third, state that matters is owned by services registered in Provider; widgets stay thin and re-render off notifier changes rather than holding data themselves.
Documentation map
- Architecture
The layer model, dependency rules, and how Provider wires services together.
- Startup & bootstrap
From
main()to the home page: config loading, service init order, and the attestation gate. - Device attestation
iOS App Attest and Android Play Integrity: ceremonies, per-request headers, and 403 recovery.
- Accounts & tokens
Sign-in paths, secure token storage, rotation, and the single-flight refresh design.
- Representatives by location
The core flow: geocoding, SHA-gated sync, SQLite queries, images, bills, and boundaries.
- Contacting a representative
Server-side form delivery with polling, and the pre-filled WebView fallback.
- Community — The Town Hall
ZIP-derived hubs, feeds, anchored posts, and moderation surfaces.
- Caching & local storage
Every store the app writes: SQLite schema, file caches, secure storage, and TTLs.
- Bug & crash reporting
Consent-first crash capture, foreground-session tracking, and manual reports.
- Automated draft releases
Version transitions, gated Android artifacts, read-only release notes, draft review, and safe rebuilds.
- Server API reference
All 43 server calls the app makes, with verb, caller, and auth layer.
The system at a glance
Reading these pages
Each page opens with the flow diagram, then explains the sequence and — more importantly — why it works that way. Design decisions that are easy to trip over (why the query cache key embeds a dataset version token, why iOS signs every request but Android doesn't, why crash reports need a session marker) get called out where they live. Exact endpoint paths, storage keys, TTLs, and limits are collected in reference tables so the prose can stay readable.
Everything is static: diagrams are pre-rendered SVG and the stylesheet is local, so these files can be read offline, from a git checkout, or attached to a ticket without any network dependency.
Companion repo: the Go backend (civiq-backend-svr) maintains its own architecture documentation. The API contract described here must stay consistent with that repo; cross-repo changes belong in both decision logs.