Inspiration

Every habit tracker ends the same way. You do the thing, you tap a checkbox, and a checkmark appears. A checkbox has never felt like anything.

That bothered me, because finishing something is the best part. Reading a chapter, drinking the eighth glass of water, finally filing the tax return. Those moments deserve a payoff, and they shouldn't all get the same one. So I flipped the usual priority around. In SubAtomic, the completion experience is the product.

What it does

You finish a habit by holding it. As you hold, the row charges up, and when you let go at full charge it pays off. A ribbon gets cut and thrown across the screen in confetti. A light swells under the row and goes out. A row shakes, then tears itself apart and comes back done.

Each habit gets its own animation, so reading can get a ribbon and water can get a glow. There are six of them: Spin, Strike, Emoji, Ribbon, Glow and Glitch. Emoji throws whichever three emoji you type in.

Your year lives on a dial instead of a grid of squares. It spirals out from the center, one spoke per week, with a mark for every habit you finished that day in that habit's own color. You can spin it with a finger, tap a month to bring it round, or zoom right into a single day.

Just as important is what's missing. There are no streaks to lose, no badges, no notifications, no ads, no analytics and no account. Your habits live on your phone and work in airplane mode.

The free version lets you pick one animation to keep during setup. SubAtomic Pro, through RevenueCat, unlocks the full catalog and all five app colors for $1.99 a month or $19.99 a year. Every habit you've already made stays free.

How we built it

SubAtomic is Kotlin Multiplatform with Compose Multiplatform, so Android and iOS share the same UI code. SQLDelight stores everything locally and is always the source of truth. RevenueCat handles the subscription on both stores with a single subatomic_pro entitlement.

Every animation is drawn by hand in Compose Canvas and shaders. No Lottie, no video. That matters because the animations aren't playing on top of the list. They are the row. Each one is a single composable that wraps the habit's label and decides everything about it: the shape it's clipped to, the color behind it, the color of the text, and the haptics. The row itself draws nothing at all.

The animation reads its state (progress, phase, completion and a few others) as State<T> rather than raw values. Reading .value inside the draw lambda means only the draw phase re-runs on every frame, instead of recomposing every row on screen sixty times a second.

Some payoffs are too big for a row. For those, the screen hosts two effect layers with the content sandwiched between them. Glow's ring of light sits below the rows, so it reads as light coming out from under them. Ribbon deals half its confetti to each layer, so pieces fly both in front of and behind the list.

Even the store screenshots and the paywall's review screenshots are rendered from the real app, so what you see in the listing is exactly what the app draws.

Challenges we ran into

The biggest one was the gesture itself. Inside a scrolling list, a press and the start of a scroll are the same event. If the habit lit up on every touch, every flick through the list would flash the rows. So the charge waits out a short pre-roll first and quietly abandons itself if your finger travels as far as a scroll would. Consuming the down event breaks scrolling entirely, so it can't do that either.

A sneakier bug was keying the gesture on its callbacks. Those lambdas get rebuilt on every recomposition, and completing a habit recomposes the row while your finger is still on it. The gesture got torn down mid-press, the release never ran, and the charge was left parked at full. The next tap then completed instantly. That one now has its own regression test.

Cross-platform rendering had its own surprises. On iOS, Compose renders through Skia, so the shaders are SkSL rather than Metal. Keeping them byte-identical with Android's AGSL meant sticking to plain float uniforms and normalizing every coordinate by size. Then there was the radial gradient with a radius of zero. Skia on desktop draws nothing and moves on. Android throws an exception. It passed every desktop render test and only crashed once the paywall's carousel put a resting Glow row on a real device.

The hardest challenge wasn't technical, though. The first eight animations I built were competent, and nobody would choose a habit for any of them. I cut the catalog down to just Ribbon and rebuilt the rest to that standard. A small catalog worth pressing beats a big one that isn't.

Accomplishments that we're proud of

The animations. Ribbon especially. When you hold an already finished habit, it cuts the old ribbon to nothing the instant you press and lays a new one down as you charge, so replaying it has the same build-up as the first time. Finished habits stay pressable on purpose. Sometimes you just want to feel it again.

I'm also proud of the paywall. You reach it by tapping an animation you haven't unlocked, so the wall doesn't ask you to imagine anything. The animation plays itself on a loop, with its confetti thrown across the whole screen. Nobody buys a completion they haven't felt.

And I'm happy with how fair the subscription is. If Pro lapses, every habit keeps its animation id. It plays your free pick in the meantime, and resubscribing restores every assignment without a single write.

What we learned

Cutting is a feature. Streaks, badges, notifications, accounts and most of the original animation catalog all got cut, and the app is better for each one. A streak turns a habit into a score to protect, and one missed day deletes the score.

On the technical side, I learned to test on a real device early and often. Desktop previews are great for iterating on animations, but Android and iOS each disagree with the desktop in their own ways. I validated the long press on a physical phone before any database existed, because if that felt wrong, nothing built after it would matter.

What's next for SubAtomic: Habit Builder

More animations. That's the promise of Pro, and every new one goes to existing subscribers too.

Beyond that, sync across devices is already built on Firebase and waiting behind a feature flag, along with reminders, which are stored and editable but not yet scheduled. Both will only land if they can do it without nagging. The app never asking where you've been is part of the point.

Built With

Share this project:

Updates

Submission history