Mobile App · 2024
Pulse Fitness App
Users churned in week one because every tap needed a connection — useless in a basement gym. Pulse was rebuilt offline-first: log a full session on airplane mode, sync when you're back.
Client
Pulse Fitness
Industry
Health & Fitness
Timeline
10 weeks
My role
Mobile Developer
The problem
Users dropped off after week one because the app needed a connection for every single action.
The solution
Offline-first workout tracking with sync, streaks and smart push reminders that bring people back on rest days.
+58%
Week-4 retention
4.8★
Store rating
12k
Active users
What I built
Offline-first logging
Local SQLite is the source of truth; the server is a sync target.
Streaks & rest days
Streak logic that survives planned rest days instead of punishing them.
Smart reminders
Push notifications scheduled from the user's real training pattern.
Progress graphs
Volume, 1RM estimates and body metrics over any time range.
Architecture
- React Native (Expo) with a typed local SQLite layer
- Supabase Postgres + Auth as the sync backend
- Delta sync protocol with per-row updated_at and tombstones
- Expo Notifications for scheduled reminders, EAS for OTA updates
Hard problems
Conflicting edits
Last-write-wins per field with a vector clock on the set level — no more lost reps.
Battery drain
Sync batched into a single WorkManager job every 15 min instead of a live socket.
Code from the build
A few real excerpts from the repository.
export async function pushChanges() {
const dirty = await local.query<Row>(
"select * from sets where dirty = 1 order by updated_at asc limit 500"
);
if (!dirty.length) return;
const { error } = await supabase.from("sets").upsert(
dirty.map(stripLocalFields),
{ onConflict: "id" }
);
if (error) return scheduleRetry(error);
await local.exec("update sets set dirty = 0 where id in (?)", [dirty.map(d => d.id)]);
}export function streak(days: Date[], restDays: number[] = [0]) {
let count = 0;
for (const day of eachDayBack(new Date())) {
if (restDays.includes(day.getDay())) continue; // planned rest never breaks it
if (!days.some(d => isSameDay(d, day))) break;
count++;
}
return count;
}"Week-4 retention went from 22% to 35%. The offline rewrite paid for itself in one month."
How it was built
- Discovery call, scope and fixed timeline agreed up front
- Custom design system built in Figma before development
- Typed, reviewed codebase with CI checks on every commit
- Deployed with monitoring, backups and post-launch support