Skip to content
All posts

How I ship Android apps as a solo developer in 2026

An actual end-to-end launch playbook for shipping a Kotlin + Compose Android app alone in 2026 — scope, build, Play Store listing, and the boring bits that decide whether you ever launch.

MFKAPPS 7 min read

The hardest part of shipping a solo Android app in 2026 isn’t the code. Compose is mature, Play’s tooling is good, and there are more libraries than anyone has time to read. The hard part is finishing. The scope creeps, the polish drains the calendar, and one day you realize the app you started three months ago is technically more impressive and still not on the store.

I’ve shipped two apps this way, with a third in the pipeline, and I’ve finally settled on a playbook that gets me from idea to live listing in about four to six weeks. None of it is clever. All of it is about saying no to things.

1. Scope ruthlessly — one screen, one verb

Before I write any Kotlin, I do a single exercise: describe the app in one sentence with one verb. Granyn — log an expense in two taps. Hydrame — get a calm nudge before you forget to drink water. Subly — snap a bill and have the subscription extracted on-device. If the sentence has an “and” in it, the app is two apps.

This single sentence becomes the rule for every feature decision. Charts? Only if they support the verb. Cloud sync? Only if not having it breaks the verb. Settings? The fewer the better. The most underrated trick in solo development is not building things.

If your one-sentence pitch has an “and” in it, you’re building two apps.

A real way to enforce this: write the Play Store listing — title, short description, the 80-character subtitle — before you write any code. If you can’t sell the app in 80 characters, you can’t build it in eight weeks either.

2. Pick a tiny, boring stack

For Android in 2026, my whole stack is intentionally tiny:

  • Kotlin + Jetpack Compose (no XML layouts, no fragments)
  • Single-activity with androidx.navigation.compose
  • Room with Flow and suspend functions for everything local
  • DataStore (preferences flavor) for settings
  • WorkManager for background, AlarmManager only where exactness is required
  • Material 3 for components, with a custom color scheme
  • Hilt for DI — but only if the app is big enough to need it (Granyn yes, a tiny utility no)

That’s the whole list. No KSP-heavy generators, no networking layer unless the app actually talks to a server, no third-party UI kits. The rule: a new dependency has to earn its line in build.gradle.kts with a one-sentence justification I’m willing to write down.

3. Build the boring screens first

Counter-intuitive but true: I build the least exciting screens before the hero feature. Empty states, settings, onboarding, “no data yet” placeholders. They reveal the data model and the navigation graph faster than anything else, and they’re the screens you’ll dread later if you save them for the end.

The hero feature comes third or fourth. By the time I get there, the data layer is already designed by the boring screens that needed to read from it.

A minimal scaffold I reuse on every project:

@Composable
fun App() {
    val navController = rememberNavController()
    Scaffold(
        bottomBar = { AppBottomBar(navController) },
    ) { inner ->
        NavHost(
            navController = navController,
            startDestination = Routes.Home,
            modifier = Modifier.padding(inner),
        ) {
            composable(Routes.Home) { HomeScreen() }
            composable(Routes.Stats) { StatsScreen() }
            composable(Routes.Settings) { SettingsScreen() }
        }
    }
}

That’s the whole “architecture” of half my apps.

4. Lock the data model early, migrate forever

Schema migrations are the silent killer of indie apps. The fix is to think of the database as a public API — once it’s in production, every change is a migration with a test. Room makes this manageable:

@Database(
    entities = [Expense::class, Category::class],
    version = 4,
    autoMigrations = [
        AutoMigration(from = 1, to = 2),
        AutoMigration(from = 2, to = 3, spec = MigrateNotesField::class),
        AutoMigration(from = 3, to = 4),
    ],
    exportSchema = true,
)
abstract class AppDatabase : RoomDatabase() { ... }

The single most useful thing I do: exportSchema = true and commit those JSON files to git. Then a Room-managed test catches schema regressions before they ever hit a user device. I cannot count the number of bad afternoons I’ve been spared by this.

5. Manual testing on real phones, automated only where it matters

Solo dev testing pyramid: a handful of pure unit tests for the bits that are easy to test and impossible to debug otherwise (currency math, date math, the OCR field-extraction in Subly). Zero UI tests. Heavy manual smoke testing on three real phones: a Pixel, a Samsung mid-range, and one really cheap Xiaomi. The cheap phone is non-negotiable — it’s the one your users actually have.

The Samsung and Xiaomi will both surface real issues that don’t show up in the emulator. Notifications killed by aggressive battery optimization, exact alarms quietly rescheduled, vibration patterns that fire 200 ms late. If your app needs reliable notifications (Hydrame does), test on a Samsung or you’ll find out after launch.

6. The Play Store listing is half your app

Spend a weekend on the store listing. Title and short description are the SEO. Screenshots are the conversion. The feature graphic is the first 1.6 seconds someone has with your brand.

A realistic checklist:

  • Title with the primary keyword (don’t be cute — “Granyn — Budget & Expense Tracker” beats “Granyn”)
  • Short description (80 chars) with the verb and the one differentiator
  • Long description with three to five short paragraphs, sub-headers, and emojis used sparingly
  • Screenshots at 1080×2400 or higher, with the first screenshot being your hero shot. Most users never swipe.
  • Feature graphic (1024×500). Keep text minimal — it’s a banner, not a billboard.
  • Privacy policy URL — required. Host it yourself (GitHub Pages works) so you can edit it without a release.
  • Content rating — fill out the questionnaire honestly. It’s faster than you think.
  • App categorization — Data Safety form. Take this seriously; it’s the public-facing version of your privacy policy.

The Data Safety form is the one most people rush. Fill it in like you’re explaining to a lawyer. If you can’t tick “no data collected”, explain exactly which third party (Firebase, AdMob, Play Billing) processes which field. Google checks against the runtime behavior more strictly each year.

7. Launch quietly, watch the funnel

Soft-launch in your home country first. Don’t announce it. The first 50 installs are your beta — pay attention to crash reports, ANRs, and the install/uninstall ratio. If retention at day 1 is under 30%, fix the onboarding before you do any marketing.

The first thing I check every morning in Play Console for the first two weeks:

  • Crash-free users %
  • ANR rate
  • Vitals (excessive wakeups, frozen frames)
  • 1-star reviews — read every word. They are gold.

Then I roll out wider — to the rest of Europe, then global. Staged rollout (10% → 25% → 50% → 100%) is free; use it.

8. The boring after-launch work

Two weeks after launch, the real work starts: responding to reviews, fixing the small bugs that the first 500 users found, and resisting the urge to start the next app. Updating once a week with small, honest changelogs builds a kind of trust that no clever marketing can match.

This is also where ASO compounds. Keywords in the title and description, a steady stream of small updates, and a slow trickle of organic reviews — none of it is glamorous, all of it works.

What I’d do differently next time

A short list, since hindsight is the best teacher:

  1. Write the Play Store listing first. Treat it as the spec. I’m not joking.
  2. Bigger empty states from day one. A blank screen kills first impressions; a thoughtful empty state buys you a second open.
  3. Onboarding gets one screen. Maybe two. If you need three, you have a UX problem to fix in the app, not a longer onboarding.
  4. Set up Crashlytics on day one. Yes, even for a tiny utility.
  5. Privacy policy hosted on its own domain or GitHub Pages. You’ll update it more often than you think and you don’t want a Play Console re-review for every tweak.

The whole playbook is unromantic on purpose. The romance of indie development is the freedom to ship; the playbook is what protects that freedom from your own scope creep.

If you’re somewhere in the middle of a half-built Android app right now, the most useful thing you can do this week is delete a feature. Then go write the Play Store description. The launch is closer than you think — most of what’s stopping you is on your side of the screen.