Syed Uzaif/ Product Lab
Index / all experiments
Experiment 01 / Production systemProduction · live on Android & iOS

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% fasterInitial load: 3.2s → 1.76s
One platform / three surfaces

Student app

SimpliEd Student app home with modules for circulars, news, events, attendance, timetable and gradebook

Teacher app

SimpliEd Teacher app dashboard showing today's classes, quick actions, pending grading and the day's schedule

Attendance / QR

SimpliEd attendance flow scanning a student QR code and marking the student present

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
01

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.

02

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.

03

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
04

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.
05

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?
06

Decisions

Approaches weighed
API-only, online-requiredrejected

Simplest to build, but unusable when connectivity drops mid-class.

Cache-on-read onlyrejected

Helps reads, but writes made offline are still lost — unacceptable for attendance and grading.

Local database as source of truth + durable outboxchosen

More upfront work, but reliable under weak connectivity.

Decision 01

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.
Decision 02

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.
Decision 03

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.
07

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.
Offline-first write path (Teacher app)
  1. User actione.g. marks attendance, enters a grade
  2. Encrypted local DBDrift + sqlite3mc — the source of truth; UI updates instantly
  3. Outbox queuedurable, ordered record of pending writes
  4. Background syncreplays the queue when connectivity returns
  5. REST APIserver accepts the reconciled writes
  6. 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.

08

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.
09

Reflection

What I learned
  • 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.
What I would improve
  • Deeper automated test coverage around the sync/outbox edge cases.
  • More explicit conflict-resolution UX for the rare case where server and device disagree.
Next steps
  • Extend the offline-first patterns to more modules.
  • Strengthen automated release checks so the store cadence stays safe as the team grows.