Layouts and Jetpack Compose

The XML view system and the Compose alternative: how a screen is described, measured and redrawn in each model.

The XML view hierarchy

In the classic toolkit a screen is a tree of View objects, usually declared in XML and inflated at runtime. Layout is a two-pass measure and layout walk down that tree.

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/title"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <Button
        android:id="@+id/action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/press"
        app:layout_constraintTop_toBottomOf="@id/title" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • 0dp with two opposing constraints means match the available space — the idiomatic way to stretch in ConstraintLayout.
  • @string/ and @color/ references keep text and colours in resources so they can be translated and themed.
  • Deeply nested layouts cost real time on every frame. ConstraintLayout exists to flatten what would otherwise be nested LinearLayouts.

Compose: UI as functions

Jetpack Compose replaces the view tree with composable functions. You call functions that describe the UI for the current state; when state changes, Compose re-runs only the functions that read it.

@Composable
fun Greeting(names: List<String>, modifier: Modifier = Modifier) {
    Column(modifier = modifier.padding(16.dp)) {
        names.forEach { name ->
            Text(text = name, style = MaterialTheme.typography.bodyLarge)
        }
    }
}

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "Tapped " + count + " times")
        Button(onClick = { count++ }) { Text("Tap me") }
    }
}

Three ideas do most of the work. Composable functions are ordinary functions annotated and called only from other composables. State held with remember plus mutableStateOf makes a read trigger recomposition. Modifiers chain layout and appearance without wrapper views.

💡
State reads must happen inside a composable for recomposition to be tracked. Lifting state up — passing a value and an onValueChange callback instead of mutating inside the child — keeps components previewable and testable.

Which one to use

AspectXML viewsJetpack Compose
Unit of UIView instance in a treeComposable function call
Updating the UIFind view, then mutate itChange state, re-run the function
Layout modelSingle measure, then layoutMulti-pass, no wrapper views needed
ReuseCustom view, XML includeExtract a composable or use a slot parameter
ToolingLayout editor, previewsPreviews with @Preview and live edit
  • New screens in a modern app are usually Compose. Existing XML screens keep working and can host composables through ComposeView.
  • Both models coexist in one app, which is why so many production codebases contain both.
  • Prefer one mental model per screen. Mixing them inside a single screen is where most confusion starts.

FAQ

Is XML layout dead?
No. It is no longer the recommended starting point, but the view system still backs it, and Compose itself is hosted inside an activity whose theme and resources come from the same resource system.
Why does my Compose screen recompose too often?
Usually because a composable reads state high in the tree, or a lambda captured in a list item changes identity every recomposition. Move the read down to the smallest composable that needs it and pass stable lambdas.

Android projects and activities Permissions, storage and publishing

Last refreshed 2026-09-18.