StreakFire Week 1–4: What 30 days of building taught me
12 TestFlight users, 58% day-3 retention, one scrapped Core Data migration, and 5 things I got wrong before I got them right.
I shipped StreakFire’s first TestFlight build in week 3. The app was functional. The streak math worked. The widget refreshed. Four testers opened it on the same day I sent the invite link.
That felt good for about 48 hours. Then week 4’s retention data came in and I realised the gap between “working” and “working well enough that strangers come back without prompting” is where all the real work lives.
Thirty days of building an iOS habit tracker in public taught me five things I didn’t expect going in. A few of them contradict advice I’d read and believed. One of them I’m still not entirely sure I’ve resolved. The full metrics and architecture decisions are in the StreakFire week 1–4 case study. This post is the lessons underneath the decisions.
Lesson 1: Scrap the database decision early, even if it hurts
On day 3 of week 1, I was 6 hours into a Core Data implementation. The schema was set up, the persistent container was initialised, and I was wiring up the first fetch requests. Then I stopped and asked a question I should have asked on day 1: what happens when a user gets a new iPhone?
With Core Data and no iCloud sync, the answer is: they lose everything. I knew I eventually wanted sync, so the real question was whether I wanted to build the sync layer myself or hand it to Apple. I scrapped the Core Data work and moved to CloudKit’s private database.
Six hours wasted, but the alternative was worse — rebuilding the data layer in week 3 or 4, after the widget and the streak engine were already coupled to Core Data’s fetch patterns. CloudKit required learning a different mental model (CKRecord instead of NSManagedObject, async saves instead of synchronous saves to a context), but the iCloud sync came for free and so did the conflict resolution.
The lesson is not “always use CloudKit”. The lesson is: nail your persistence and sync story before you write any feature code. Changing it later is not a refactor, it’s a rewrite.
Lesson 2: Build the widget in week 2, not week 5
WidgetKit has a constraint that’s easy to underestimate until you hit it: the widget extension is a separate binary with its own memory budget, its own timeline, and no live access to your app’s in-memory state. If your data model isn’t designed for that from the start, adding a widget later means redesigning the data model — which means rewriting anything that touches it.
I built the first WidgetKit timeline entry at the start of week 2, before the streak calculation engine was finished. That felt premature. It wasn’t. Designing for the widget’s stateless read model forced me to keep HabitRecord serialisation clean and to think about what the minimum readable data for a widget was. Those constraints improved the main app’s architecture.
One thing I got wrong here: I assumed iOS 16, 17, and 18 handled widget refresh budgets the same way. They don’t. I burned most of a day on widget refresh failures on a device running iOS 16 before I found the OS-version-specific documentation. The fix was minor — an explicit relevance score on the timeline entry — but finding it cost time I hadn’t budgeted.
Next time, I’d write the WidgetKit layer as a standalone test harness first and run it on the oldest supported OS version before connecting it to the main app.
Lesson 3: 14 unit tests for streak math is the right number, not excessive
Before week 3, I would have called 14 unit tests for a streak calculation function over-engineered for an MVP. I was wrong about this.
Streak math has at least 4 genuinely tricky edge cases: timezone crossings (did the user check in “today” in their timezone or UTC?), DST transitions (days that are 23 or 25 hours long), the grace-day logic (one missed day shouldn’t reset the streak, but two should), and the interaction between all three at once. Each of those is a case where an off-by-one error produces a silent wrong answer — the user’s streak disappears or stays when it shouldn’t, and they don’t know why.
I wrote 14 tests before submitting to TestFlight in week 3. Every one of them passed on the first TestFlight build. Not because I’m unusually careful but because the tests surfaced 3 bugs during development that I would otherwise have shipped. The crash-free rate across 847 sessions was 99.8%. I don’t think that’s a coincidence.
The lesson: for any calculation that involves time and user state, test coverage is not optional in v1. The cost of a silent wrong answer in a habit tracker is a user who loses their 30-day streak on a timezone crossing at New Year and never opens the app again.
Lesson 4: The onboarding flow I was proud of was the wrong design
I built a 3-screen onboarding flow in week 4. I was happy with it. It explained the grace-day mechanic, asked for notification permissions with a rationale screen first, and ended with a “start your first habit” prompt. I thought it was clear.
The first 3 testers who went through it in week 4 all skipped the permission rationale screen. They tapped through as fast as possible to get to the app. One of them told me the rationale screen felt like a warning before an ad — exactly the opposite of what I intended.
I rewrote the notification permission flow twice before it felt right. The version that worked was shorter: one sentence, plain language, no design emphasis on the permission request, shown only when the user set their first reminder. That’s contextual permission. The in-context ask converted better than the upfront rationale screen, and it required less friction at the start.
I’m still not entirely sure the current onboarding is correct. Day-3 retention for the week-4 cohort is 58% — that’s 4 out of 7 testers returning on day 3. Encouraging, but 7 testers is not a sample size I’d bet on. The week-5 priority is notification personalisation, which should move that number.
Lesson 5: Shipping to TestFlight in week 3 was the only decision that mattered
I could have spent week 3 polishing the flame animation. The visual design of the streak indicator — the fire that survives one missed day — was the thing I’d spent the most time thinking about conceptually. I had 6 Figma frames for it.
Instead I shipped build 1 to TestFlight at the start of week 3 with a placeholder visual and onboarded 4 testers from my network. The feedback I got in the first 48 hours changed the onboarding design and surfaced a real edge-case bug in the notification scheduling logic that I hadn’t hit in the simulator.
The 6 Figma frames for the flame visual are still mostly unused. The feedback from 4 real users on real devices was worth more than any amount of solo iteration on the visual. I already knew this in theory. Experiencing it again in practice — watching testers tap straight past the thing I’d been obsessing over to ask about something I’d barely thought about — is what actually changed my behaviour.
The lesson is embarrassingly obvious in retrospect: the thing you’re most attached to is often not the thing your users care about most. Shipping early makes that gap visible at a point when you can still do something about it.
Retrospective
Thirty days in: 12 TestFlight users, 99.8% crash-free across 847 sessions, 58% day-3 retention for the week-4 cohort. Those numbers are small and I’m not pretending otherwise. The users are all people who know about the project — not cold App Store discovery.
What they do tell me: the core mechanic works. People are opening the app and logging habits without me nudging them. The grace-day thesis — that a streak should survive one human mistake — is holding up in actual use.
The flame visual is still being iterated. I fell in love with the brand before I’d proven the core mechanic, and that’s the most classic founder mistake there is. The mechanic works. The brand can wait.
Week 5 is notification personalisation and the first App Store Connect submission. The goal is a public listing before the end of April. Whether that happens on schedule or slips, the build-in-public record will say so.
If this resonates, the weekly diary entries go deeper on the day-to-day decisions. You can also subscribe to the newsletter for updates when new posts land.
Written by Brandon Ta
Indie Hacker & AI Builder. Building AI tools and sharing the journey publicly.
Comments
Comments
Join the conversation! Share your thoughts, ask questions, or connect with other readers below.