Android / Kotlin
Android is the platform with the widest range of devices in the world and the least control over any of them. Android development is shaped less by its language than by one constraint: the system owns your app's lifetime, can rebuild your screen whenever it likes, and can kill the whole process while the user is somewhere else. This page covers the lifecycle, Kotlin and coroutines, Compose against Views, and what fragmentation actually costs.
- Lifetime
- Owned by the system
- Language
- Kotlin, on the JVM
- UI
- Compose, Views underneath
The system owns your screen
Rotate the device and Android destroys your activity and builds a new one. So does changing the language, resizing a window in split screen, switching theme, or connecting a keyboard. The object your code was holding is gone and a fresh instance is running the same screen. Every new Android developer meets this the same way — as a form that empties itself the first time someone turns the phone sideways.
There is a second, harsher version of the same thing. While your app is in the background, the system may kill its process outright to reclaim memory — and when the user comes back, Android recreates the app at the screen they left, from nothing. This is not an edge case on a mid-range phone with a dozen apps open. It is why the storage question has three answers rather than one, and why picking the wrong one produces a bug you cannot reproduce on your own device.
The same reasoning governs background work. An app that is not on screen has no right to keep running, and the platform has tightened this with every release — background limits, doze mode, and manufacturers who add their own restrictions on top. Work that must eventually happen goes to WorkManager, which persists it and runs it when the system permits, surviving both process death and reboot. Work that must be visible while it happens needs a foreground service and a notification, because the user is entitled to know.
Kotlin, and coroutines in particular
Kotlin is the language of Android now — Google's recommendation, the language the documentation is written in, and what all the modern APIs are designed around. It runs on the JVM and calls Java libraries directly, so adopting it in an existing Java codebase is file by file rather than all at once. Two of its features earn their place immediately: nullability is part of the type system, so a value that can be null cannot be used as if it cannot, and data classes remove the boilerplate that made Java models tedious.
Coroutines are the part that changes how Android code is written. A suspending function can pause without blocking its thread, so asynchronous work reads top to bottom instead of as nested callbacks. The concept that makes them safe rather than merely convenient is structured concurrency: every coroutine belongs to a scope, and when the scope is cancelled every coroutine inside it is cancelled too. Launch work in viewModelScope and it stops automatically when the ViewModel goes away — the leak of a background task updating a screen that no longer exists is designed out rather than remembered.
On top of coroutines sits Flow, a stream of values over time — the natural shape for a database query that should re-emit when the data changes, or a search field that should query as the user types. For screen state specifically, StateFlow holds a current value and emits updates, which is exactly what a UI needs: something that can be read immediately when the screen is rebuilt and observed for changes after that.
Compose and the View system
Jetpack Compose is the modern UI toolkit and the recommended one: you write composable functions that describe the interface for a given state, and the framework recomposes what changed. It replaces XML layouts, findViewById, adapters and the whole apparatus of holding view references and mutating them by hand — for a list, it is the difference between a RecyclerView adapter with a view holder and a LazyColumn with a lambda.
| Concern | Views (XML) | Compose |
|---|---|---|
| How state reaches the UI | You find the view and set the value, and keep the two in sync yourself. | The UI is a function of state; there is nothing to keep in sync. |
| Lists | RecyclerView, an adapter and a view holder — several files per list. | LazyColumn with an item lambda, virtualised by default. |
| Where it bites | Leaked references and state that drifts out of step with the screen. | Unstable parameters causing recomposition of things that did not change. |
| Reality in 2026 | Most existing screens, and plenty of libraries that still expect it. | Where new work goes; the two interoperate in both directions. |
The pattern the platform documentation now assumes, and which is worth adopting whichever UI toolkit you use: state flows down and events flow up. A composable receives its state as a parameter and reports events as lambdas, holding no state of its own where it can avoid it. That makes it previewable, testable and reusable, and it puts every decision about what the screen should show in one place — the ViewModel — which is also the only place that survives the recreation this page opened with.
How this shows up in real delivery
Fragmentation is real but it is not what people imagine. The API level problem is largely solved — the support libraries backport most of it, and minSdk is a number you choose with data from your own users. What genuinely differs is the hardware underneath: a phone with two gigabytes of memory and a slow processor will kill your process sooner, drop frames your test device never drops, and expose every allocation in a scroll handler. Test on a cheap device, because a meaningful share of your users are on one.
Release is more forgiving than on iOS in one way and stricter in another. Google Play review is usually faster, and staged rollouts let you release to one per cent of users, watch the crash rate in the console, and halt without shipping a new build. On the other hand, policy enforcement is automated and unsentimental — target API level deadlines, permission declarations, and data safety forms that must match what the app actually does. Both of those are calendar items, not engineering ones, and they are the ones teams forget.
Where it degrades
- Screen state held in the activity, which empties the first time the device is rotated.
- Nothing written to saved state, so the app returns blank after the system reclaims the process.
- An activity context held by a singleton or a static field, leaking the whole view hierarchy.
- Background work started outside a lifecycle scope, which keeps running with nowhere to deliver.
- Deferred work run in a service rather than WorkManager, so it dies with the process.
- Permissions requested at launch instead of in context, which trains users to decline.
- Testing only on a flagship, which hides every memory and frame problem your users actually have.
When to use it
Use it when
- Products whose users are mostly on Android — in much of the world that is the great majority of them.
- Apps that need deep system integration — background sync, widgets, sharing, custom keyboards, accessibility services.
- Anything that must run acceptably on low-end hardware, where a native build has room the alternatives do not.
- Teams already on the JVM — Kotlin is a short move, and the tooling will be familiar.
Avoid it when
- Building both platforms natively with one small team, which doubles the surface without doubling the people.
- Designs that assume a fixed screen size — this platform has every size, and the lifecycle reacts to all of them.
- Architectures that assume the process stays alive, which is the assumption Android specifically does not honour.
- Products that cannot commit to the annual target API level upgrade, which is a hard deadline on this platform.
Found this useful?
Share it with someone who is working on the same problem.