Android Architecture in 2026: What We Ship at Scale
Rashad Cureton
Founder, Cure Consulting Group

The Android Stack Has Matured — Your Architecture Should Too
Five years ago, Android architecture meant picking between MVP and MVVM and fighting with Fragment lifecycles. In 2026, the platform has converged around a clear set of opinions:
- Jetpack Compose for UI (XML layouts are legacy now)
- Kotlin Coroutines + Flow for async and reactive data
- Hilt for dependency injection
- Room for local persistence
- Retrofit + kotlinx.serialization for networking
But having the right libraries isn't architecture. Architecture is how you organize code so that a team of 5-15 engineers can ship features without stepping on each other.
The Architecture We Ship
After building the Ford Connected Vehicle Platform and multiple enterprise Android apps, we've converged on a layered architecture that balances purity with pragmatism:
Layer 1: UI (Compose)
- Screens are stateless composables that receive state and emit events
- ViewModels hold UI state as
StateFlow— never exposeLiveDatain new code - UI state is a single sealed class per screen — no scattered mutable fields
- Navigation uses type-safe routes with compose-navigation 2.8+
// UI State as a single sealed hierarchy
sealed interface DashboardState {
data object Loading : DashboardState
data class Success(
val metrics: List<Metric>,
val lastSync: Instant
) : DashboardState
data class Error(val message: String) : DashboardState
}Layer 2: Domain
- Use cases encapsulate single business operations
- Each use case has one public method —
operator fun invoke() - Use cases can depend on other use cases but never on Android framework classes
- This layer has zero Android imports — it's pure Kotlin
Layer 3: Data
- Repositories are the single source of truth
- Offline-first by default: Room is the primary data source, network is a sync mechanism
- Repository pattern abstracts data sources so the domain layer doesn't know (or care) where data comes from
Offline-First Isn't Optional Anymore
In connected vehicle systems, we learned this the hard way: cellular connections in parking garages, tunnels, and rural areas are unreliable. Your app needs to work without the network.
The pattern:
- Room database is the source of truth for all displayed data
- WorkManager handles background sync — retry policies, constraints, and exponential backoff
- Conflict resolution uses last-writer-wins with server timestamps for simple models, CRDTs for collaborative data
- Sync status is exposed in the UI — users should always know if they're seeing fresh or cached data
Get insights like this in your inbox
Practical tips on AI, mobile & cloud — no spam.
// Repository with offline-first pattern
class VehicleRepository(
private val dao: VehicleDao,
private val api: VehicleApi,
private val syncManager: SyncManager
) {
fun getVehicles(): Flow<List<Vehicle>> = dao.observeAll()
suspend fun refresh() {
val remote = api.fetchVehicles()
dao.upsertAll(remote.map { it.toEntity() })
syncManager.markSynced("vehicles")
}
}Compose Performance: The Things Nobody Tells You
Compose is powerful, but it has performance traps that bite at scale:
- Stability matters. If your data classes aren't stable (containing
List,Map, or mutable fields), Compose will recompose unnecessarily. Use@Immutableor@Stableannotations deliberately.
- LazyColumn is not RecyclerView. It doesn't recycle views — it recomposes. For lists over 500 items, you need to think about
keyparameters and content type optimization.
- State hoisting isn't free. Hoisting state too high causes unnecessary recompositions of parent composables. Use
derivedStateOffor computed values.
- Image loading needs care. Coil 3's
AsyncImagewith properContentScaleand placeholder handling is the standard. Pre-size images server-side when possible.
Testing Strategy
Our testing pyramid for Android:
- Unit tests (70%): Domain layer use cases, repository logic, ViewModel state machines. All use cases are tested with fake repositories — not mocks.
- Integration tests (20%): Room database operations, API serialization (using MockWebServer), WorkManager chains.
- UI tests (10%): Critical user flows only — login, checkout, core feature paths. Use Compose testing APIs, not Espresso.
The key insight: ViewModels become trivially testable when they depend on use cases (pure Kotlin) instead of repositories directly. Your ViewModel test becomes: "given this use case returns X, assert the state is Y."
Shipping Reliably
At Ford, we shipped to devices with Android 8 through 14, across hundreds of hardware variants. Here's what keeps your release pipeline sane:
- Feature flags for every new feature — Firebase Remote Config or your own
- Staged rollouts — 1% → 10% → 50% → 100%, with crash rate monitoring between each stage
- Baseline profiles — pre-compile critical paths for faster cold starts
- R8 full mode — aggressive code shrinking and optimization
- App Bundle — not APK, for optimal download sizes per device
When to Go Native vs. Cross-Platform
We're frequently asked about Flutter or KMP. Our answer:
Go native when:
- You need deep platform integration (camera pipelines, Bluetooth LE, background services)
- Performance is non-negotiable (financial transactions, real-time data)
- Your team already knows Kotlin
Consider KMP when:
- You have both iOS and Android targets with significant shared business logic
- Your shared logic is data transformation, networking, and business rules — not UI
- You can accept a 3-6 month learning curve for the team
We rarely recommend Flutter for enterprise apps. The abstraction layer creates debugging complexity that becomes expensive at scale.
Building an Android app that needs to work at scale? Book a free architecture review — we've shipped Android to millions of devices and know where the pitfalls are.
Written by
Rashad Cureton
Founder & Principal Engineer
Rashad is the founder of Cure Consulting Group. Previously an engineer at JP Morgan, Ford, Clear, NYT, Kickstarter, and Big Nerd Ranch. He builds full-stack web and mobile apps for startups and companies of every size.
Related Articles

Kotlin Multiplatform in Production: When It Works and When It Doesn't
KMP promises shared code across Android, iOS, and backend. After shipping KMP in production for enterprise clients, here's an honest assessment of where it shines and where it breaks down.
8 min

Mobile App Development: Native vs Cross-Platform in 2026
The native vs. cross-platform debate has shifted dramatically. KMP, Flutter, and React Native have all matured — but 'it depends' isn't useful advice. Here's a concrete decision matrix.
10 min

The Real Cost of Technical Debt: A CFO's Guide
Technical debt isn't just an engineering problem — it's a financial one. Here's how to quantify it, communicate it to the board, and decide when paying it down makes business sense.
10 min