The Local-First Rebellion: Unpacking firebadnofire/Milestones

How a minimalist Android tracker trades architectural bloat for raw speed and data sovereignty.

6 min read · firebadnofire/Milestones

A sleek minimalist pocket watch resting on a heavy iron anvil, contrasting with a massive and overly complex Rube Goldberg machine in the background. This represents the simplicity of Milestones against modern architectural bloat.
In an ecosystem obsessed with scale, Milestones proves the enduring value of simple, localized utility.
Key Takeaways

The Anti-Bloat Philosophy

Modern mobile development has a bloat problem. Simple utilities often require complex toolchains, cloud synchronization, and hefty local databases to perform basic tasks. The firebadnofire/Milestones repository rejects this paradigm entirely. It is a pure utility designed to track time elapsed since or remaining until specific events.

Instead of adopting the heavy, prescribed architectures favored by modern tutorials, it respects the user's battery, storage, and privacy. It proves that a functional, reliable Android application can be built without a massive footprint.

A Database in a String

The most controversial technical choice in Milestones is its persistence layer. The application completely bypasses Room, SQLite, and modern ORMs. Instead, the MilestoneStorage.kt file handles data by serializing the entire dataset into a single JSON string.

Using org.json.JSONArray and JSONObject, the app maps its Kotlin data classes manually. The resulting string is saved directly into SharedPreferences. While unscalable for thousands of records, this zero-boilerplate approach is highly performant for the dozens of items a typical user tracks.

The zero-boilerplate serialization pipeline bypasses standard databases entirely.

Defeating the Day-Flipping Bug

Time math in software is notoriously brittle. A common issue in simple trackers is the day-flipping bug, where an event created at 11:00 PM shows an incorrect day count the next morning. Milestones solves this elegantly within MilestoneAdapter.kt.

The daysSince helper function normalizes both the current time and the start date to exactly midnight (00:00:00) before calculating the difference. This ensures that crossing the midnight boundary always accurately registers as a single elapsed day.

Normalizing timestamps to midnight prevents standard timezone calculation errors.

Retro Architecture, Bleeding-Edge Tooling

While the architecture resembles a classic Android application with a single 'God Object' MainActivity.kt orchestrator, the build environment is fiercely modern. The project targets SDK 36, tracking the absolute bleeding edge of Android development.

It utilizes Kotlin 2.0, Version Catalogs for dependency management, and DynamicColors for Material You theming. It also features a unique build-time Gradle task that automatically copies a root-level appicon.png into the generated resources.

val generateAppIcon by tasks.registering(Copy::class) {
    from(rootProject.file("appicon.png"))
    into(appIconResDir.map { it.dir("drawable") })
}
A close-up of a pair of tweezers delicately extracting a single, perfectly legible paper receipt from a small wooden card catalog drawer. Behind it sits a massive, locked bank vault door covered in warning signs.
The SharedPreferences JSON approach provides total data transparency, contrasting sharply with locked-down SQLite databases.

The Cost of Simplicity

This minimalist approach is not without tradeoffs. By relying on a single Activity and manual JSON parsing, Milestones sacrifices the automated lifecycle management and scalability provided by standard Android Jetpack libraries.

However, for a utility of this scope, those tradeoffs represent a calculated victory. The application remains lightning fast, perfectly readable, and completely independent of proprietary cloud services.

FeatureMilestones ArchitectureStandard Jetpack Architecture
PersistenceSharedPreferences + JSON StringRoom + SQLite
State ManagementActivity-driven ViewBindingViewModel + StateFlow
Data PortabilityRaw JSON exportEncrypted backup or Cloud sync
Boilerplate CodeZeroHigh (DAOs, Entities, Migrations)