One Thing, Done Properly

Terje Rutgersen · Mar 2026 · privacy · android

Kofte is a knitting counter. That should be enough, but the app economy has trained people to expect a caveat. A knitting counter with social features. A knitting counter powered by AI. A knitting counter that needs an account to save your row count.

Kofte counts your rows. It tracks your stitches. It knows what a gusset is. It does not need your email address.

Why this exists

My wife knits. Every app she tried wanted something from her before it would give her anything back. Sign up. Subscribe. Watch an ad between your heel turn and your gusset pickup. Knitting is thousands of years old. The counter should not be this complicated.

I wanted to build something that respects the person using it. Not in a landing-page way, in an engineering way. No telemetry. No analytics SDK phoning home while you knit a sock. No account creation screen standing between you and a row counter.

What it does

You create a project. You pick a template (sweater, socks, hat, scarf, blanket) or start blank. Templates give you the right parts: a sweater gets a body, yoke, and two sleeves. Socks get cuff, leg, heel flap, heel turn, gusset, foot, toe. Each part gets a counter. Tap to increment. That is the core loop.

The interesting bit is shaping. Knitting patterns are not "knit 200 rows." They involve structured increases and decreases. Increase 2 stitches every 4 rows, 5 times starting at row 10. Kofte lets you define these rules and tracks your stitch count as you go. When you hit an action row, the counter lights up gold. No arithmetic in your head while you are also counting stitches.

Counters can track stitches within a row too. Set your stitches-per-row, and the counter advances the row automatically when you reach the end. Tap through your stitches, rows take care of themselves.

Zen mode. Full screen, big number. Tap anywhere to count. For when you are in the zone and do not want to think about UI.

10 cottagecore themes with light and dark variants. Knitting apps should look like they belong in the craft, not in a corporate dashboard.

What it does not do

The INTERNET permission is not in the manifest. No analytics, no crash reporting, no Firebase, no tracking pixels, no "anonymous usage data." Your knitting data lives on your device and nowhere else.

No accounts either. No sign-up, no password, no OAuth, no "continue with Google." You open the app and you are already in.

The pro upgrade is a one-time purchase. You pay once, you own it, every future update included. No ads, no subscription. The business model is making something good enough that people want to pay for it.

No cloud sync. Deliberate, not lazy. Uninstall the app, the data goes with it. No server to breach, no database to leak, no third-party processor to audit.

How it is built

Single-activity Compose app. Room for persistence, DataStore for preferences, Hilt for DI. Standard modern Android stack. MVVM with a repository layer. ViewModels expose StateFlow, the database is the single source of truth, transactions keep counter operations atomic.

The database is on version 8. Eight hand-written migrations, each tested. The schema covers projects, parts, counters, counter actions (undo history), sessions, and shaping rules. Every counter change gets recorded so you can undo it. The app keeps the last 10 actions per counter and trims the rest.

Session tracking remembers where you left off. Open a project and it shows which part you were on and for how long. Come back within an hour and it rolls the time into the same session. Small feature, but it matters when you knit in short bursts across a week.

The money part

Free with one project and one counter per part. Pro removes those limits and adds themes, notes, and history. Costs less than a ball of yarn.

Billing uses Google Play's billing library, which is the one place where the app touches a network. Purchase verification goes through Google's servers. The purchase state caches locally in DataStore so the app works offline immediately after. Airplane mode, pro features still work. The billing library is the only dependency that phones home, and it is mandatory if you want to sell on the Play Store.

I wrote about this tension in The Platform Phones Home Anyway. You can build a perfectly private app, but the distribution platform is instrumented by default. The honest move is to acknowledge that baseline and draw a hard line at adding anything on top.

Localization

Kofte ships in 16 languages. Norwegian, French, German, Spanish, Czech, Swedish, Italian, Portuguese, Ukrainian, Icelandic, Maori, Japanese, Malay, Indonesian, and Traditional Chinese for Hong Kong. No translation service. No runtime i18n library. Android's built-in resource system and a folder per locale.

Adding a language is a content task. Translate 212 strings with correct knitting terminology, add three lines of Kotlin, done. Wrote a separate post about it: How Kofte Ships 16 Languages With Zero Libraries.

The knitting vocabulary matters. A gusset is マチ in Japanese. Yarn is 毛冷 in Hong Kong Cantonese. These are not machine translation outputs. Getting the craft words right is the difference between a localized app and one that was run through a translator.

What I learned

Build for someone you actually watch use it. My wife loses track of increases mid-row, so shaping rules exist. She kept hitting the wrong button in the normal UI, so zen mode exists. Every feature came from watching real knitting happen and noticing where the app got in the way.

Small software can be finished software. Kofte does not have a social feed or a pattern marketplace or AI-generated knitting suggestions. It counts rows, tracks stitches, tells you when to increase. That is enough.

Charge money for your work. Not monthly. Not with ads. Just a price. People who knit understand paying for good tools. A single payment respects both sides. The user is not a revenue stream, and the developer is not running a charity.

There is no reason a knitting counter needs a server. No reason it needs your location, your contacts, or your advertising ID. The defaults should be private. Everything else is a choice you made on behalf of someone who trusted you enough to install your app.