Building and improving a learning platform with 10,000+ cumulative downloads
Owning mobile features and releases across a production education platform — two live Student and Parent apps and an offline-first Teacher app with an encrypted local database and durable sync. Production work under confidentiality.
- Role
- Flutter Developer (promoted from intern within 3 months)
- Timeline
- Sep 2024 — Present
- Platforms
- Android · iOS
- Key result
- ~45% faster — Initial load: 3.2s → 1.76s
Student app

Teacher app

Attendance / QR

Demo accounts shown — production data stays private.
- observation symbol
- observation
- open question symbol
- open question
- decision symbol
- decision
- rejected symbol
- rejected
- progression symbol
- progression
- result symbol
- result
Observation
Teachers lose work when classroom connectivity fails.
SimpliEd is a production education platform (LMS) used by students, parents, and schools. It spans two live consumer apps (Student and Parent) and a newer offline-first Teacher app. I joined as an intern and grew into a full-time Flutter Developer, working inside an existing product and team rather than on a greenfield build.
Problem
Schools depend on the platform during the working day, often on patchy classroom or commute connectivity. Teachers can't lose attendance or grades because a signal dropped, and every change to the consumer apps ships to real users — so the work is as much about not breaking a live system as it is about adding to it.
Context · The consumer apps were already in the stores with a real user base when I joined. The Teacher app was the newer surface, where offline reliability mattered most. My work happened continuously against production: incremental features, performance and stability fixes, and coordinated store releases — never a rewrite.
Ownership
I work within a product team alongside backend engineers, QA, and design. Downloads and overall usage are platform-level outcomes the whole team contributes to — my direct ownership is the mobile layer: features, offline behaviour, performance, and release execution.
What I owned
Personally executed
- Flutter feature implementation across Student, Parent, and Teacher apps
- Offline-first mobile architecture on the Teacher app
- Encrypted local database as the single source of truth (Drift + sqlite3mc)
- Durable outbox / retry system for writes made offline
- Startup-performance investigation and the initial-load restructure
- App bundle-size reduction
- Store-release execution (TestFlight, Play Internal Testing)
What I contributed to
Alongside others
- Requirement clarification on incomplete tickets
- REST API coordination with the backend team
- QA coordination and internal testing rounds
- User-flow improvements for the consumer apps
- Investigating and reproducing production issues
Team / product outcome
Not solely mine
- 10,000+ cumulative downloads across the Play Store and App Store (whole platform, whole team)
- A sustained 99%+ crash-free session rate
- Ongoing Android and iOS release cadence
Constraints
- Every change ships into a live app with real users — regressions are expensive.
- Classroom and commute connectivity is unreliable, so the Teacher app must work fully offline.
- It's production education software: student data, internal URLs, and private metrics are confidential and sanitised before anything is shown publicly.
- Schema had to evolve on-device without data loss as features grew.
Questions
- What must keep working when the network doesn't — and what can wait for reconnect?
- Where should the source of truth live so a dropped connection never loses a teacher's work?
- Which part of a slow cold start is actually worth optimising first?
- How do we evolve the on-device schema repeatedly without risking existing data?
Decisions
Simplest to build, but unusable when connectivity drops mid-class.
Helps reads, but writes made offline are still lost — unacceptable for attendance and grading.
More upfront work, but reliable under weak connectivity.
Make the encrypted local database the single source of truth on the Teacher app, not a cache in front of the API.
- Why
- Teachers cannot depend on constant connectivity during the working day.
- Rejected
- API-only persistence.
- Trade-off
- More upfront architecture and sync logic, in exchange for the UI staying fully usable offline.
- Outcome
- Work stays available and queued during weak connectivity — a dropped connection delays sync, it doesn't lose data.
Queue offline writes in a durable outbox and replay them in order on reconnect, with retry and reconciliation.
- Why
- A write made offline has to survive app restarts and land exactly once.
- Rejected
- Fire-and-forget network writes.
- Trade-off
- Extra complexity handling ordering, conflicts, and idempotency.
- Outcome
- No silent data loss across 28+ schema migrations.
Profile the real cold start with DevTools before optimising, rather than guessing.
- Why
- Startup cost is rarely where you assume it is.
- Rejected
- Speculative micro-optimisation.
- Trade-off
- A slower start to the fix, but changes targeted the actual bottleneck.
- Outcome
- Initial load cut ~45% — 3.2s → 1.76s.
System
- Offline-first data layer: an encrypted SQLite database (Drift + sqlite3mc) as the single source of truth for the Teacher app.
- Durable outbox that queues writes made offline and replays them in order on reconnect, with retry and reconciliation against the REST API.
- Reactive UI with Riverpod 3.x reading from the local database, so screens update the moment local state changes — online or off.
- Platform layer behind clean service abstractions: FCM push with deep-link routing, secure auth with encrypted token storage, and Crashlytics / Analytics / Remote Config.
- 28+ on-device schema migrations shipped with zero data-loss incidents by treating the local database as authoritative.
- Startup restructure informed by Flutter DevTools profiling; separate work reduced the app bundle size.
- User actione.g. marks attendance, enters a grade
- Encrypted local DBDrift + sqlite3mc — the source of truth; UI updates instantly
- Outbox queuedurable, ordered record of pending writes
- Background syncreplays the queue when connectivity returns
- REST APIserver accepts the reconciled writes
- Retry & reconcilefailed writes retry; server state merges back down
Because the local database is authoritative, the screen never blocks on the network — a dropped connection delays sync, it doesn't lose work.
Published write-up · How I Structured an Offline-First Flutter Application ↗
production work under confidentiality — screenshots and details are sanitised (student data, internal URLs, private metrics removed) before publishing.
Results
- Initial load (~45% faster)
- 3.2s → 1.76sInitial load (~45% faster)
- App bundle-size reduction
- ~28%App bundle-size reduction
- Crash-free session rate
- 99%+Crash-free session rate
- Schema migrations, zero data loss
- 28+Schema migrations, zero data loss
- Cut initial load time ~45% (3.2s → 1.76s) and reduced app bundle size ~28%.
- Sustained a 99%+ crash-free session rate on the surfaces I work on.
- Shipped 28+ on-device schema migrations with zero data-loss incidents.
- Team/product outcome: the platform has 10,000+ cumulative downloads across the Play Store and App Store.
Reflection
- On a live app, migrations and releases are the risky part — treating the local database as the source of truth is what made 28+ migrations safe.
- Profiling before optimising matters: the real startup cost wasn't where I'd have guessed.
- Clean service abstractions kept the platform layer testable and swappable as requirements changed.
- Deeper automated test coverage around the sync/outbox edge cases.
- More explicit conflict-resolution UX for the rare case where server and device disagree.
- Extend the offline-first patterns to more modules.
- Strengthen automated release checks so the store cadence stays safe as the team grows.