The Local-First Rebellion: Unpacking firebadnofire/Milestones
How a minimalist Android tracker trades architectural bloat for raw speed and data sovereignty.
- Milestones rejects modern Android architectural bloat by relying on a single-Activity structure and zero dependencies.
- The application bypasses Room and SQLite entirely, serializing its data model directly into a JSON string stored in SharedPreferences.
- A clever midnight-normalization function prevents the notorious day-flipping timezone bug inherent to simple date mathematics.
- Despite its retro architecture, the project targets the bleeding-edge SDK 36 and utilizes Kotlin 2.0 with Material You theming.
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.
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.
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") })
}
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.
| Feature | Milestones Architecture | Standard Jetpack Architecture |
|---|---|---|
| Persistence | SharedPreferences + JSON String | Room + SQLite |
| State Management | Activity-driven ViewBinding | ViewModel + StateFlow |
| Data Portability | Raw JSON export | Encrypted backup or Cloud sync |
| Boilerplate Code | Zero | High (DAOs, Entities, Migrations) |