A fitness app people actually keep using
A mobile fitness app that turns daily workouts, activity history, and goals into a simple routine people stick with past the first month.
What the engagement involved
- Industry
- Healthcare & Dental Practices
- Duration
- 12 weeks
- Cooperation model
- Fixed price, phased
Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.
What we were brought in to do
Users logged workouts for two weeks, then dropped off. We redesigned the app around a habit loop that survives real life.
An app with strong installs and a retention curve that fell off a cliff at day fourteen, the point where the novelty of a new tracker runs out and the friction has to be worth it. The team had been adding features to fix it, which had made logging slower. The engagement was scoped around a single number, thirty-day retention, and everything was judged against it.
Product Design & Build
Where the old way broke
Generic tracking felt like a chore, so people lost sight of progress and quit after a few weeks.
Logging a workout took eleven taps and forty seconds because every field was empty every time, including the ones that had held the same value for six weeks. Session data showed people abandoning mid-log more often than skipping a workout entirely. They were turning up at the gym and giving up on the app: the worst possible failure for a habit product.
We designed a focused logging flow, a clear history view for spotting trends, and lightweight goal-setting that slots into a daily habit without adding friction.
15-Second Workout Logger
Fast workout logging built around habit formation and as few taps as possible.
The logger opens on the workout you did last, pre-filled with last session's weights, so the common case is confirm-and-close. Everything writes to a local SQLite store first and syncs after, which makes it usable in a basement gym with no signal. Nothing in the flow blocks on the network, and no screen in it asks a question the app could answer from history.
- Opens pre-filled from the last session's numbers
- Local-first writes; the network is never in the path
- No field the app could fill from history
Mid-session with no signal: set three of bench press at 82.5 kg with last week's 80 beside it, weight and reps already filled so the set is one confirm, the rest timer running, and every set saved on the phone to sync later.
The morning after a missed day: the streak held with Thursday dimmed in the two-week view, three short sessions offered instead of a lecture, and six months of sessions a week showing the one week off in August barely registering.
Consistency & Streak Tracker
Visual trend history that rewards showing up often, whatever the intensity.
The history view plots frequency over volume, because the behavior that actually predicts people staying is turning up. Streaks are forgiving by design: a missed day dims and never resets, after testing showed hard resets pushed users to abandon the month entirely. Trends are computed on a rolling four-week window, so a good week can't flatter a bad month.
- Frequency plotted ahead of volume or intensity
- Forgiving streaks: a missed day dims, never resets
- Rolling four-week trend window
Phone Health Source Sync
Health sources: HealthKit read the same way as Health Connect, five permissions each asked at the moment it's needed with what the app does without it, a run from watch and phone counted once, and everything working with all five off.
Automatic biometric and activity data integration across native iOS and Android APIs.
HealthKit on iOS and Health Connect on Android are read through one normalization layer, so a step count means the same thing whatever wrote it. Deduplication runs on source and timestamp, because a phone and a watch both reporting the same run was doubling people's weekly totals. Permissions are asked for at the point of use, never at first launch, and the app works fully without them.
- One normalization layer over HealthKit and Health Connect
- Source-and-timestamp dedup for phone plus watch
- Permissions requested at point of use, never at launch
What we built together
- 01
Cut logging to under 15 seconds for the most common workouts
Fifteen seconds came from session recordings: below it people log in the gym, above it they mean to do it later and don't.
- 02
Designed a streak and trend view that rewards consistency
History plots frequency over volume, and a missed day dims a streak instead of resetting it. Hard resets had been making people abandon the month.
- 03
Added gentle, well-timed reminders that stop short of nagging
Reminders fire against the user's own established pattern, never a fixed hour, and stop after two ignored in a row instead of escalating.
- 04
Shipped and iterated against 30-day retention weekly
Every change was judged against the weekly install cohort, which is how two features that felt right were built and removed inside the engagement.
Operational results after launch
+37%
30-day retention
+2.1x
Workouts logged/user
4.8★
App store rating
Retention is thirty-day, measured per install cohort so a marketing push can't flatter it. Workouts per user is the median, not the mean, because a small number of very heavy users move the average and never moved the problem. The store rating is the rolling figure for the six months after launch.
Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.
Phase by phase
Phase 1: Retention & Behavior Audit
User Churn & Habit Loop Analysis
Analyzed 30-day retention logs and interviewed churned users to isolate logging friction points and habit decay triggers.
- Retention Audit Report
- Habit Loop Model
- Mobile Usability Study
Phase 2: Micro-Interaction Design
Sub-15s Logging & Trend UX
Designed sub-15 second workout logging interactions, streak milestone badges, and non-intrusive notification triggers.
- Figma Design System
- Micro-Interaction Prototype
- Notification Strategy
Phase 3: Cross-Platform Build
React Native & Health Data Engineering
Engineered native iOS and Android apps with phone health-source auto-syncing and local SQLite offline persistence.
- React Native Application
- Health Data Sync Engine
- Local Persistence Layer
Phase 4: Store Launch & Iteration
App Store Deployment & Retention Sprints
Shipped to App Store and Google Play, running weekly retention iteration cycles that increased 30-day active retention by 37%.
- App Store & Play Store Submissions
- Weekly Analytics Dashboard
- Release Sign-off
The retention console: eight monthly intakes since March with week-eight retention climbing from 38 to 57 percent and the release that shipped to each, September's 3,192 opened, and the seven nudge rules with the two switched off.
About our collaboration
- 01
A cross-functional team of 5 worked on a fixed price, phased basis over 12 weeks, covering Mobile design, React Native build, Habit loops. We held a standing mid-week checkpoint and wrote decisions down in place of status meetings. Nothing shipped without a live demo first.
Retention was measured weekly against the cohort that installed that week, and every change shipped behind that number, not a release plan. Two features were built and removed inside the engagement because the cohort didn't move, which was the whole point of measuring weekly instead of reviewing at the end.
What we'd carry into the next one
Simplifying workout logging to under 15 seconds lifted 30-day user retention by +37%.
Fifteen seconds isn't a nice-to-have threshold: below it logging happens in the gym, above it people mean to do it later, and later doesn't arrive.
Visual streak tracking and trend rewards increased workouts logged per user by 2.1x.
Rewarding frequency over intensity is what made the streak survivable. The previous streak rewarded personal bests, which nobody can produce daily.
Phone health-source auto-syncing removed manual data entry friction entirely.
Auto-sync removed the fields people were most likely to guess at, which improved the data as much as the experience: guessed numbers had been polluting the trends.
A streak that resets punishes the people most likely to leave.
Play one member's two weeks under the old rule, where a miss zeroes the count, and under the one that shipped, where it dims and three short sessions are offered the next morning. Beside it, week-eight retention for the intake that lived with each. Switch tabs, or use the arrow keys once one is focused.
Day 14 of 14 · one member
10sessions in the streak
Carrying on
The dimmed day stays in the history and the count never went back to zero. Offered the morning after:
Mobility 10 · 10 minWalk it off · 15 minUpper express · 20 minWeek-eight retention · measured
March intake38%
Hard-reset streak, eleven-tap loggerOctober intake57%
Forgiving streak, short sessions, and every release since MarchThe cohort data found that three sessions in the first week is what decides week eight. That's why a miss should cost a dimmed day, never the whole count.
The two weeks shown are illustrative, replayed fast, and follow what testing showed hard resets doing. The retention figures are measured per monthly install intake; October's carries every release between the two intakes, not the streak change on its own.
From a confirmed set to a retention number nobody can flatter
React Native on iOS and Android. The phone owns the write, the server owns the history, and the cohort owns the verdict on every release.
- 01 · TriggerA set confirmed, a sample readThe logger opens on last session's numbers, so the common case is confirm-and-close, under fifteen seconds.
- 02 · LocalSQLite on the phoneEvery write lands locally first. Nothing in the logging flow blocks on the network, so losing signal loses nothing.
- 03 · NormaliseHealthKit + Health ConnectOne normalization layer, so a step means the same whatever wrote it. Dedup on source and timestamp stops watch-plus-phone doubling.
- 04 · StateNode.js + PostgreSQLHistory kept as frequency over a rolling four-week window. A missed day dims a streak; reminders stop after two ignored.
- 05 · JudgeWeekly install cohortThirty-day retention per cohort and workouts as a median, so a marketing push or a few heavy users can't flatter a release.
Kept sessions, asked permissions, honest totals
Offline-first writes, consent in the moment & deduplicated health data
A logged set never waits on signal
Everything writes to a local SQLite store first and syncs after. Nothing in the logging flow blocks on the network, so a basement gym with no signal still keeps every set.
Health access asked for, never assumed
HealthKit and Health Connect permissions are requested at the point of use, not at first launch, and the app works fully without any of them.
One run counted once
Deduplication runs on source and timestamp, so a phone and a watch reporting the same run no longer double a weekly total, and synced numbers replace the guesses that had polluted trends.
Building an app people need to still be opening in week eight? Scope your build in three minutes.
Scope your buildNearby engagements
Web PlatformsThe ransomware attack that became a four-hour non-event
A ransomware attack encrypted the primary patient records server. Drilled offsite backups and a written runbook turned what could have been a scramble into a full restore inside the recovery time objective.
Healthcare & Dental Practices · Ongoing retainer
E-commerceA cart that won't check out until the prescription is real
An online pharmacy where a prescription is a first-class record with its own lifecycle, and prescription-only items simply can't leave the cart without one.
Healthcare & Dental Practices · 20 weeks
Web PlatformsThe right blood type isn't enough: it has to be someone who can get there
A donor register that matches each request on blood-group compatibility and on whether the donor can actually reach the hospital. One platform serves a web admin and a mobile client from the same records.
Healthcare & Dental Practices · 16 weeks
Let's talk
Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.













