Jetpack Compose: fundamentals, Composable functions and State

Author: IT Sectr Published: 2026-02-21 Reading time: 8 min

Jetpack Compose is a modern declarative UI framework from Google for Android development, released in stable version 1.0 in July 2021. Compose replaces imperative XML layout and View system with Kotlin functions annotated with @Composable. According to Google I/O (2025), Compose is used in 55% of new Android projects, and the average UI code size is reduced by 30–50% compared to XML. The framework automatically redraws only the changed parts of the screen when State changes, eliminating manual calls to notifyDataSetChanged or findViewById.

Key Takeaways

  • Jetpack Compose — Google's declarative UI framework for Android in Kotlin (@Composable functions instead of XML layout).
  • @Composable — function annotation describing a piece of UI that can accept parameters and read State.
  • State and MutableState — reactivity mechanism: when State changes, Compose recomposes only dependent Composable functions.
  • remember — caching function for values between recompositions; without it State resets on every update.
  • Modifier — chain of settings (padding, size, clickable, background) controlling the appearance and behavior of a Composable.

What is Jetpack Compose?

Jetpack Compose is a Google UI framework built on reactive programming principles. Instead of Activity with XML layout, FragmentManager, and ViewBinding, the developer describes the interface through Kotlin functions annotated with @Composable. Compose does not use LayoutInflater — UI is pure Kotlin functions compiled into bytecode. The framework consists of several libraries: Compose UI (basic components Text, Button, Column, Row), Compose Foundation (Material Design, Gestures, Focus), Compose Material3 (Material You with Dynamic Colors) and Compose Runtime (State, Side Effects, Coroutines). Compose works on Android API 21+ (Android 5.0) and is fully compatible with existing View/XML code through ComposeView — an AndroidView adapter.

Compose vs View System

View System (2008) uses a hierarchy of View objects with manual management: the developer creates an XML layout, finds Views via findViewById, implements an Adapter for RecyclerView and calls notifyDataSetChanged when data changes. Compose describes UI functionally: when state changes, the framework recomposes only the changed Composable functions, computes the diff and applies minimal changes to Canvas. Compose performance is comparable to View System, and in scenarios with complex lists (LazyColumn) it often surpasses RecyclerView by skipping unnecessary elements through the key parameter. At IT Sectr, Compose is used for new modules of Android applications with minSdk 24+.

Composable functions: building blocks of UI

@Composable — an annotation that turns a regular Kotlin function into a UI building block. Composable functions can call other Composable functions, forming a component tree. Unlike View, Composable functions have no state by default — they are stateless and redraw on every change of input parameters or State. The Compose Compiler transforms @Composable functions into code that can be interrupted and resumed (positional memoization) — this allows Compose to restart only the changed parts without fully rebuilding the tree.

kotlin
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun UserProfile(name: String, avatarUrl: String) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        AsyncImage(
            model = avatarUrl,
            contentDescription = "User avatar",
            modifier = Modifier.size(48.dp)
        )
        Spacer(modifier = Modifier.width(12.dp))
        Text(
            text = name,
            style = MaterialTheme.typography.titleMedium
        )
    }
}

The UserProfile function accepts name and avatarUrl as parameters, builds a row with an avatar and name. Modifier.fillMaxWidth() stretches the component to full width, .padding(16.dp) adds padding. MaterialTheme.typography.titleMedium uses the system Material 3 style. When name or avatarUrl parameters change, Compose recomposes UserProfile — recreating the component with new values.

State and Recomposition in Jetpack Compose

State in Compose is any value whose change triggers recomposition (restart) of dependent @Composable functions. The basic approach is mutableStateOf(), creating a MutableState<T> with Compose's snapshot system support. Snapshot is a change tracking mechanism: when State is written, Compose marks the current Snapshot as dirty, and the recomposition scheduler restarts only the Composable functions reading that State. For observable collections use mutableStateListOf() and mutableStateMapOf(). Compose supports State from Lifecycle-Aware components via collectAsState() for Kotlin Flow and observeAsState() for LiveData.

kotlin
@Composable
fun LikeButton() {
    var liked by remember { mutableStateOf(false) }
    var count by remember { mutableStateOf(42) }
    
    Button(
        onClick = {
            liked = !liked
            if (liked) count++ else count--
        },
        colors = ButtonDefaults.buttonColors(
            containerColor = if (liked) MaterialTheme.colorScheme.primary
                else MaterialTheme.colorScheme.surfaceVariant
        )
    ) {
        Text("♥ $count")
    }
}

The code creates a "Like" button with liked (Boolean) and count (Int) state stored via mutableStateOf(). On press, liked is toggled and the counter increments/decrements — Compose automatically recomposes LikeButton. The by delegate allows using State like a regular variable: liked = !liked instead of liked.value = !liked.value. The button color reactively changes between primary (liked) and surfaceVariant (not liked).

remember: caching between recompositions

remember — a Compose function that caches a value between recompositions. Without remember, every restart of a Composable function creates a new state (mutableStateOf) — this breaks application logic: on screen rotation or parent State change, the counter resets. remember accepts a calculator lambda and computes the value only on the first composition; on subsequent ones it returns the cached result. To reset the cache use remember(key) — the key determines when to recompute the value. remember(key1, key2) recomputes when any key changes. For long-lived states (surviving screen rotation) use rememberSaveable — an analog of remember that saves to Bundle via SavedStateHandle.

kotlin
@Composable
fun TimerScreen() {
    var seconds by rememberSaveable { mutableStateOf(0) }
    
    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            seconds++
        }
    }
    
    Column(
        modifier = Modifier.fillMaxSize().padding(32.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Elapsed: ${seconds}s",
            style = MaterialTheme.typography.displayMedium
        )
    }
}

rememberSaveable preserves seconds on screen rotation (configuration change). LaunchedEffect starts a coroutine that increments seconds every second. On TimerScreen recomposition (every second) seconds is not reset thanks to rememberSaveable. Use regular remember for state that should not survive configuration changes (e.g., animation flags).

Modifier: chaining Composable settings

Modifier — an immutable container object implementing the Builder pattern for configuring the appearance, behavior, and layout of Composable components. Each Modifier method call (.padding(), .width(), .clickable(), .background()) returns a new Modifier with the added element. Modifier order matters: .padding(16.dp).clickable { } — padding is applied before the click handler, clickable tracks the entire area including padding. .clickable { }.padding(16.dp) — the entire area before padding is clickable, padding shifts content inward. Modifier supports custom implementations via then(otherModifier) and compositionLocal for accessing parent settings.

kotlin
@Composable
fun CardExample() {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .clickable { println("Card clicked") },
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surfaceVariant
        )
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text("Jetpack Compose", style = MaterialTheme.typography.titleLarge)
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                "Declarative UI for Android with State and Modifier",
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}

CardExample demonstrates the Modifier chain: fillMaxWidth + padding + clickable. Card is a Material3 component with its own elevation and colors settings. The nested Column has internal padding of 16dp. Compose Material3 automatically adapts colors to Dynamic Color (Material You) on Android 12+. Card is one of 30+ built-in Material3 components in Compose.

State hoisting and Compose architecture

State hoisting — a pattern of lifting state out of a Composable function into its caller, making the function stateless. A stateless component receives data and callbacks through parameters without owning State itself. This improves reusability and testability: the component can be called with different States and tested in preview. Google recommends the architecture: ViewModel + StateFlow (or MutableStateFlow) + collectAsState() in Compose. ViewModel lifts state to the screen level, Compose renders it. Side effects (LaunchedEffect, DisposableEffect) handle one-time events.

ComponentResponsibilityTechnology
ViewModelState storage, business logicStateFlow, MutableStateFlow
Screen ComposableScreen organization, State hoistingcollectAsState(), Scaffold, LazyColumn
Stateless ComponentUI without state, pure rendering@Composable parameters + lambdas
Side EffectOne-time actions (snackbar, navigation)LaunchedEffect, SnackbarHostState

Frequently Asked Questions

How is Jetpack Compose different from XML layout?

XML layout is imperative: LayoutInflater loads XML, findViewById finds Views, the developer manually updates text/lists. Compose is declarative: UI is described by Kotlin @Composable functions, and the framework automatically redraws only the changed parts when State changes. Compose reduces UI code volume by 30–50% and does not use XML.

Do I need a separate library for navigation in Compose?

Yes, Google provides Navigation Compose — a library for declarative navigation between screens. NavController manages the route stack, composable() registers screens, navArgument passes parameters. Alternatives: Voyager (community) and Decompose (Badoo). Navigation Compose is the official standard with deep links and type-safe argument support.

What is Modifier in Jetpack Compose?

Modifier is an immutable container of decoration patterns (padding, size, clickable, background, border, clip) applied to Composable components. Each call returns a new Modifier with the added element. Order matters: .padding().clickable() applies padding before click, .clickable().padding() — the opposite. Modifier is the primary way to customize Compose components.

Does Compose work on all Android versions?

Jetpack Compose supports Android API 21+ (Android 5.0 Lollipop) through the Compose Compiler and Compose BOM. Material 3 (Dynamic Colors) is available on Android 12+; older versions use a fallback palette. For new projects, Google recommends minSdk 24+ (Android 7.0), which is fully covered by Compose.

How to test Compose components?

Google provides Compose UI Test — a library for testing on JVM (Desktop) and emulator. Key functions: composeTestRule.setContent { } to render a component, onNodeWithText() to find elements, performClick()/performTextInput() for actions. Espresso tests are compatible with Compose via ComposeTestRule, but Compose UI Test is the preferred and faster approach.

Summary

  • Jetpack Compose — Google's declarative UI framework for Android with Kotlin @Composable functions instead of XML (stable since 2021).
  • @Composable — annotation that turns a Kotlin function into a UI component; the Composable function tree replaces the View hierarchy.
  • State and mutableStateOf — the foundation of Compose reactivity via the Snapshot system; recomposition affects only dependent functions.
  • remember caches a value between recompositions; rememberSaveable preserves State on configuration changes.
  • Modifier — a chain of settings (padding, size, clickable, background) forming an immutable container; modifier order matters.
  • State hoisting — a pattern of lifting state upward for stateless components; ViewModel + StateFlow + collectAsState() is the recommended architecture.
  • Material 3 with Dynamic Colors (Android 12+) is the standard Compose theme with automatic adaptation to device wallpaper.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also