MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Running 22+ live Android apps as a solo developer is not a scaling problem — it's an architecture problem. Every tool I built and every pattern I adopted was a direct response to a specific failure that became unacceptable at fleet scale.
On this page
Running 22+ live Android apps as a solo developer is not a scaling problem. It's an architecture problem.
Scaling problems are about capacity — more servers, more bandwidth, more team members. Architecture problems are about cognitive load — how do you keep the whole system in your head when it's too large to keep in your head?
Everything I built and every pattern I adopted was a direct response to a specific failure mode that became unacceptable as the fleet grew.
I didn't adopt Clean Architecture because it's the recommended pattern. I adopted it because, around app number five, I had a bug I couldn't locate.
The ViewModel was calling a repository. The repository was calling a database. The database was calling a content provider. There was also a network layer somewhere. Where the bug was — I couldn't tell without reading every layer.
Clean Architecture solves one concrete problem: it makes breakpoints visible. When data/domain/presentation layers are explicit and enforced by package boundaries, you know immediately which layer the bug lives in. Not because the code is "clean" in an aesthetic sense, but because the seam is there to put a breakpoint on.
After that, every new app started with the same structure. Not because I'm disciplined, but because discovering a bug while simultaneously remembering where things live is too expensive.
Around app number eight, I had a crash that took three days to trace. LiveData's observer lifecycle management was getting into a state where observers were attached, disposed, and re-attached in a sequence that produced stale data in a fresh screen.
The issue isn't that LiveData is wrong — it's that managing observer lifecycles manually is a source of bugs that Kotlin Coroutines + StateFlow eliminates by design.
StateFlow is lifecycle-unaware by itself. You collect it with
repeatOnLifecycleSince switching the fleet to StateFlow, zero crashes from observer lifecycle issues across all 22+ apps.
Around app number twelve, I updated the Kotlin version in two apps and forgot the third. That third app then failed to compile when I tried to add a feature three weeks later.
Three weeks of drift. One library version difference. Complete build failure.
The fix was a shared
libs.versions.tomlThe version catalog doesn't prevent you from overriding per-app, but it makes overriding intentional rather than accidental.
I had two apps sharing a keystore. I rotated the credentials for one of them and accidentally invalidated the signing config for both.
Play Store won't accept an update signed with a different key than the original upload. Both apps were effectively locked out of updates until I traced back through what had happened.
Per-app keystore isolation is now absolute:
.jkskeystore.properties.gitignore.jkskeystore.propertieslocal.propertiesThe overhead of managing 22+ separate keystores is real. It's less than the overhead of recovering from a signing config collision.
I submitted an app update with the same version code as the previous release. Play Store rejected it. I had updated the version name in
build.gradle.ktsThe fix was moving version state out of
build.gradle.ktsconfig/version.propertiesVERSION_MAJORVERSION_MINORVERSION_PATCHVERSION_CODEval versionProps = Properties().apply {
load(rootProject.file("config/version.properties").inputStream())
}
android {
defaultConfig {
versionCode = versionProps["VERSION_CODE"].toString().toInt()
versionName = "${versionProps["VERSION_MAJOR"]}.${versionProps["VERSION_MINOR"]}.${versionProps["VERSION_PATCH"]}"
}
}Version bumps are one-line edits in one file. The version code is incremented explicitly — there's no way to forget it.
By app number twenty, the release checklist was twelve steps. Each step was manual. Each step was a potential failure point.
DroidForge encodes the signing config generation and Gradle orchestration into a Claude Code plugin — it runs the checklist, not me.
PlayCraft encodes the Play Store submission structure — listing copy format, release notes format, Data Safety section checklist — into a repeatable workflow.
PrivacyPilot generates the privacy policy per app and deploys it to GitHub Pages at a predictable URL (
sudarshanchaudhari.github.io/[appname]-privacy-policy/Each tool was built after a specific failure. Not as a philosophical preference for automation, but because the manual process was producing failures at scale.
At around sixty repositories, I started losing commit context. I'd make changes to fix a bug in app A, intend to commit them, get pulled to app B by something urgent, and then two days later have uncommitted changes in three repos with no memory of what they were for.
PushyUncommit scans all local repositories for uncommitted changes, shows the diffs, and generates structured atomic commit messages based on what it finds. The Android companion app surfaces repo state on mobile without opening a terminal.
The tool doesn't prevent uncommitted changes — it makes them visible before they become lost context.
22+ apps is not impressive because of the number. It's notable because of the constraint: one engineer, full ownership, no handoffs.
The constraint forces every system to be:
Every pattern in the fleet is there because a failure made it necessary. The architecture is legible because legibility is a survival requirement, not an aspiration.
The clearest signal that a system is fleet-scale maintainable: can you open it after three months and know exactly where the bug is?
If the answer is no, the architecture has a problem. If the answer is yes for all 22 apps, the architecture is doing its job.
That's the only measure that matters at scale — not test coverage percentages, not architectural purity scores, not lines of code. Can you get back to productive state in under ten minutes? If yes, the system is maintainable. If no, fix the thing that makes it hard.
Sudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus