Case Study|6 min read|

Architecture Decisions Behind Chantik

Why I chose certain patterns and trade-offs when building a production construction management app.


Architecture Decisions Behind Chantik

Chantik is a construction site management app I built solo — from first commit to production. This post isn't a tutorial. It's the reasoning behind the choices I made, and what I'd do differently.

Context

Construction sites need simple tools. The people using this app are on-site, often with gloves on, dealing with dust and sun glare. They need to:

  • Track daily progress with photos
  • Coordinate between teams
  • Report issues with location context
  • Access everything offline

This shaped every technical decision.

Offline-First, Not Offline-Capable

There's a difference. "Offline-capable" means the app gracefully degrades. "Offline-first" means the app works fully offline and syncs when it can.

I chose offline-first because construction sites often have poor connectivity. The local database (Room) is the source of truth. Sync is a background concern.

This means:

  • Every write goes to Room first, then queues for sync
  • Conflict resolution follows last-write-wins with server timestamps
  • Media uploads happen in a background WorkManager chain — retry-aware, battery-conscious

The trade-off? More complexity upfront. But users never see a loading spinner for their own data.

MVVM with a Twist

Standard MVVM (ViewModel → Repository → DataSource) works well for most screens. But some features — like the photo capture → annotate → upload flow — don't fit neatly into a single ViewModel.

For these, I used a coordinator pattern: a shared state holder that multiple ViewModels observe. The coordinator owns the multi-step flow logic, and individual ViewModels handle their screen-specific concerns.

Jetpack Compose All The Way

I started with Compose from day one. No XML layouts, no fragments, no navigation component. Just Compose screens with a simple navigation wrapper.

Benefits:

  • Faster iteration — change UI, see it instantly
  • Less boilerplate — no view binding, no layout inflation
  • Better state management — UI is a function of state, period

The cost was a steeper learning curve for some patterns (like nested scrolling and keyboard handling), but the long-term velocity gain was worth it.

What I'd Change

  • Sync logic: I'd invest more in a proper CRDT-based sync instead of last-write-wins. For multi-user scenarios, timestamp ordering isn't enough.
  • Testing: I'd write more integration tests from the start. Unit tests caught logic bugs, but the sync + Room + network integration is where the real issues lived.
  • Modularization: The app grew into a single module. Breaking it into feature modules earlier would have improved build times and enforced better boundaries.

The Lesson

Architecture is about trade-offs, not best practices. Every choice I made optimized for solo development speed and offline reliability. A team of five would make different choices — and they'd be right too.

The best architecture is the one that ships.