A Flutter fitness application for tracking personal bests, workout splits, and strength progression. Built as a final-year honours dissertation project.
OneRep follows a feature-first folder structure with an offline-first data layer.
lib/
├── core/
│ ├── database/ # Drift SQLite schema and provider
│ ├── notifications/ # Local notification service
│ ├── sync/ # Supabase sync service and background worker
│ ├── theme/ # Colour palette and Material theme
│ └── utils/ # Shared utilities (date formatting)
└── features/
├── auth/ # Supabase auth — repository, providers, screens
├── profile/ # User profile (bodyweight, sex) via SharedPreferences
└── workout/
├── data/ # Repositories, badge service, strength standards
└── presentation/ # Screens: splits, session, progress, badges, exercises
State management: Riverpod (code-generation via @riverpod)
Local database: Drift (SQLite) — all data is written locally first, synced later
Remote database: Supabase (PostgreSQL with Row Level Security)
Sync strategy: Dirty-flag pattern — every table has syncedAt and deletedAt columns. Records with syncedAt = null are uploaded on manual sync or periodic background task. Deletes are soft-deleted locally and propagated upstream.
- Flutter 3.x SDK
- Dart 3.x
- A Supabase project with the schema applied
Create a dart_defines.env file at the project root:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
This file is .gitignored and must never be committed.
flutter pub getdart run build_runner build --delete-conflicting-outputsflutter run --dart-define-from-file=dart_defines.envflutter testThe test suite covers the repository layer, badge service, sync service, and strength standards calculations. All tests run against an in-memory SQLite database — no network required.
flutter build apk --dart-define-from-file=dart_defines.envflutter build appbundle --dart-define-from-file=dart_defines.envThe AAB is output to build/app/outputs/bundle/release/app-release.aab.
Metric types. Exercises are categorised as weightReps, bodyweightReps, timeOnly, or distanceTime. This drives which fields are shown in the set logger, how personal bests are compared, and how PR banners display results.
Personal best model. One PR row per exercise is maintained via an upsert conflict target on (exerciseId, reps). For non-rep-based exercises, reps = 0 acts as a sentinel slot, so the unique constraint still holds without a schema change.
Strength percentiles. Population data from Strengthlevel.com is embedded as lookup tables in strength_standards_data.dart. Percentile is interpolated linearly between the nearest bodyweight brackets. Requires the user to set bodyweight and sex in their profile.
Background sync. Workmanager schedules a weekly background task that initialises Supabase in a separate isolate and uploads dirty records. The isolate cannot use Flutter bindings, so all dependencies are manually constructed.