Jetpack Compose Made Me Rethink How I Build UI
When I started working with Jetpack Compose, the biggest change wasn't the syntax.
It was the way I had to think about UI.
With traditional Android UI, it's easy to think in terms of views:
Find a view
↓
Change its value
↓
Update another view
↓
Refresh something
`
Compose pushes you toward a different model:
text
State
↓
UI
↓
User action
↓
State changes
↓
UI recomposes
The UI becomes a function of state.
Don't make the UI the source of truth
Consider a simple screen displaying a transaction.
Instead of having the UI independently track everything, the screen can consume a state object:
kotlin
data class TransactionUiState(
val transactions: List<Transaction>,
val isLoading: Boolean,
val error: String? = null
)
Then the UI can essentially ask:
"Given this state, what should I display?"
For example:
kotlin
when {
state.isLoading -> LoadingIndicator()
state.error != null -> ErrorMessage(state.error)
state.transactions.isEmpty() -> EmptyState()
else -> TransactionList(state.transactions)
}
This is much easier to reason about than having different pieces of UI independently deciding what state the screen is in.
Events should flow in one direction
A pattern I've found useful is treating user interactions as events.
For example:
text
User taps Save
↓
onSaveClicked()
↓
ViewModel
↓
Repository
↓
Data changes
↓
New UI state
↓
Compose recomposes
The UI doesn't need to know how the transaction is stored.
It only needs to communicate the user's intent.
For example:
kotlin
Button(
onClick = {
viewModel.saveTransaction()
}
) {
Text("Save")
}
The ViewModel handles what happens next.
That separation becomes increasingly useful as the application grows.
Recomposition isn't something to fear
One of the first concepts that can feel strange with Compose is recomposition.
The important mental model is:
Your composable should be able to run again.
That means avoiding unnecessary side effects directly inside composable functions.
Instead of thinking:
text
"This function runs once."
think:
text
"This function describes the UI for the current state."
If the state changes, Compose may run the composable again.
That's not a bug.
That's the point.
State ownership matters
Another important question is:
"Who should own this state?"
Not every piece of state belongs in the ViewModel.
For example, temporary UI state such as whether a dropdown is expanded can often remain local to the composable.
But application state such as:
text
Transactions
Current balance
Loading state
Error state
usually needs a longer lifecycle and may belong higher in the architecture.
A useful rule is:
`text
Local UI state
↓
Composable
Screen/application state
↓
ViewModel
`
The exact architecture can vary, but the principle is to keep state as close as possible to where it is actually needed.
Compose doesn't eliminate architecture problems
This is probably the most important lesson.
A modern UI framework doesn't automatically create a good application architecture.
You can still build a mess with Compose.
For example:
text
Composable
↓
Database
↓
Business logic
↓
Network request
↓
More UI state
It might work initially.
But eventually the screen becomes responsible for too many things.
Compose makes it easier to write UI declaratively.
It doesn't remove the need for good separation of concerns.
What I'm learning
Working with Jetpack Compose has made me think less about manipulating UI elements and more about modeling state and events.
Instead of asking:
"How do I update this screen?"
I'm increasingly asking:
"What state should this screen represent, and what events can change that state?"
That shift is useful beyond Android.
It's a general way of thinking about interactive software.
The UI is not the application.
The UI is a representation of the application's current state.
Once that becomes your mental model, many UI problems become easier to reason about.
android #kotlin #jetpackcompose #softwarearchitecture #mobiledevelopment #programming #buildinpublic
`
Top comments (0)