← posts

I built an app for my fiancée. Then I couldn't stop rewriting it

· 4 min read

I started learning iOS development in April 2020. I was sat at home at my parents' place during Covid, couldn't work, and wanted to learn something new, so I bought an old used Mac to get started. 8 months later, in December 2020, I shipped reHydrate: a hydration tracker, my first proper app.

I didn't build it for the App Store. I built it for my fiancée (my girlfriend at the time). We were both trying to get healthier, and between the extra workouts and everything else we'd taken on, we kept forgetting to drink enough water. I wanted something that would help her keep on top of it, so she wouldn't forget. That was the whole brief.

Version 1 did exactly that and nothing more: it tracked how much water you drank in a day, logged when you drank it, and showed your goal against your running total. Reminders were in from day 1. Making them smarter came later.

The duplication mess

I didn't know what I was doing yet, and it showed. I didn't understand how to share logic between screens, so I didn't. Every function got duplicated wherever it was needed, and the same bugs kept showing up in different places. One I remember: there were multiple ways to edit a water container, and if you edited one the "wrong" way, the units wouldn't convert properly, or the change wouldn't persist if you restarted the app.

I'd built the perfect example of why you shouldn't duplicate logic, and turned it into my own personal learning ground. So I got cracking: started working out how to remove the duplication, and learned what app architecture actually was in the process.

3 rewrites, 2021 to 2024

The first rewrite came in 2021, right after that duplicated first version. I still think of it as the biggest leap: moving to something closer to a proper MVP architecture, so logic got reused instead of copy-pasted. A bigger rewrite followed in 2022. Then in 2024, the biggest one yet: a full Engine-Presenter-Model-View architecture.

Roughly, it looks like this:

Engine-Presenter-Model-View, as reHydrate uses it today.

It leans on hexagonal architecture: the engine holds the services and exposes ports, and adapters plug into those ports for things like HealthKit and the database. The presenter talks to the engine, then pushes updates to the model. The view just observes the model and re-renders when it changes; it doesn't know or care how the update happened.

That's where the app sits today. The payoff is testability. Every code path is shareable and testable, so I don't break things by accident and I'm not doing a lot of manual QA every time I touch it.

None of this happened because reHydrate needed it for its users. It has around 100 people actually using it, and I've never felt there was a bigger use case waiting to be unlocked. The rewrites happened because I needed them, as a developer. reHydrate is my portfolio app now: the one project where I can show my progression from storyboards and massive view controllers to a reactive, async/await, SwiftUI-first codebase.

Swift 6, and a testing problem I haven't solved

Since 2025 I've been migrating reHydrate to Swift 6 with strict concurrency on from the start, mainly to avoid race conditions around database updates and HealthKit. It's taking longer than I'd like. The app's sitting at a halfway point while I work through it.

The hardest part hasn't been the concurrency model. It's been migrating my tests. Strict concurrency made me second-guess parts of my test suite, and building mockable structures is harder than it used to be. I used to just mock things with a class. Now I need an actor, or a fake service that doesn't hold anything in memory, except not holding anything in memory kind of defeats the point of a mock/fake/stub/spy.

I haven't landed on a pattern that solves this. I'm still working through it and honestly not sure how I'll approach it. If you've solved this for your own Swift 6 migration, I'd like to hear how.

What's next

Liquid Glass adoption is next. Given the whole app is built around liquid, I think there's an interesting UI in there. Right now that idea is vague. I'm in the middle of building GuestLens, so reHydrate is on the back seat for now. But I want to keep it current. It's been 5 years of rewrites already; I don't see a reason to stop.

reHydrate is free on the App Store and the source is on GitHub.