Theory Learning Platform
A Flutter application for working through structured theory material, backed by a Spring Boot service and a versioned PostgreSQL schema.
Built with

Overview
A learning application for studying driving theory through structured question sets rather than a static document. The mobile client is built with Flutter so a single codebase covers Android and iOS, and it talks to a Java and Spring Boot service that owns the content model, progress data and authentication.
Problem
Driving-theory material is often distributed as long-form documents. That format is poor for repeated practice: there is no notion of what has already been covered, no way to revisit only the weak areas, and nothing works once the device drops off the network. The content also changes over time, so the app cannot ship it as a hardcoded bundle.
Goals
- Model topics, questions and answers as first-class database entities so the material can change without shipping a new app release.
- Give the learner a session-based flow with visible progress rather than an endless scroll.
- Cache the active content locally so an interrupted connection does not end a study session.
- Keep the API surface small enough to reason about and version safely.
My role
I designed and implemented both sides: the Flutter client, its state management and local caching layer, and the Spring Boot service, its persistence model and migration strategy.
Architecture
The system splits into three layers with a deliberately narrow contract between them.
Mobile client (Flutter/Dart). Screens are composed from reusable widgets and driven by a state layer that separates fetching, caching and presentation. Content retrieved from the API is written to local storage so a session can continue while offline, and reconciled the next time the client connects.
API service (Java/Spring Boot). A layered service — controllers, an application service layer, and repositories — exposing a small REST surface for topics, question sets and learner progress. Request payloads are validated at the boundary, and errors return a consistent shape so the client has a single failure path to handle.
Data (PostgreSQL/Flyway). The relational schema models the content hierarchy and progress records. Every schema change is a numbered Flyway migration checked into the repository, so a database can be rebuilt from scratch deterministically and every environment converges on the same state.
Docker is used to run Postgres locally, which keeps the development database identical to the one the service expects in other environments.
Key implementation details
- Content as data, not code. Topics and question sets are rows, not constants. Editing material is a database change, not an app release.
- Offline-first reads. The client treats the local cache as the read source and the API as the synchronisation mechanism, which removes network latency from the interaction path.
- Migrations in source control. Flyway migrations are versioned alongside the service so schema and code always move together.
- Consistent error contract. A single error response shape across all endpoints meant the client needed one error handling path rather than one per screen.
Challenges
The most involved part was reconciling local and remote state. A learner can progress through a session offline, then reconnect to content that has since changed. Handling that without silently discarding either side required deciding explicitly, per entity, which source wins and what happens to work in progress.
Schema evolution was the second: once progress data exists, migrations have to preserve it, which rules out the convenient approach of dropping and recreating tables during development.
What I learned
Pushing the content model into the database early was the decision that paid off most — almost every later change became a data change instead of a code change. It also reinforced how much of mobile development is really data synchronisation design rather than interface work.
Related projects
Interested in this kind of work?
Happy to talk through the architecture, the trade-offs or anything that is not covered above.

