Jetpack Compose UI
Android: Jetpack Compose UI Jetpack Compose is Android's modern declarative UI toolkit. Instead of XML layouts, you write Kotlin functions annotated with @Compo…
Android: Jetpack Compose UI
Jetpack Compose is Android's modern declarative UI toolkit. Instead of XML layouts, you write Kotlin functions annotated with @Composable that describe the UI. Compose re-runs composables when state changes — no manual view updates.
Composable Functions
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
// Stateful composable — owns and manages state
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Count: $count", style = MaterialTheme.typography.headlineMedium)
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
// Stateless composable — receives state as params (easier to test/reuse)
@Composable
fun CounterDisplay(count: Int, onIncrement: () -> Unit) {
Column {
Text("Count: $count")
Button(onClick = onIncrement) { Text("Increment") }
}
}
// Preview
@Preview(showBackground = true)
@Composable
fun CounterPreview() {
MyAppTheme { Counter() }
}Layout Composables
// Column — vertical stack
Column(
modifier = Modifier.fillMaxWidth().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Item 1")
Text("Item 2")
}
// Row — horizontal stack
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("Left")
Icon(Icons.Default.ArrowForward, contentDescription = null)
}
// Box — overlapping (Z-stack)
Box(modifier = Modifier.fillMaxSize()) {
Image(painter = painterResource(R.drawable.bg), contentDescription = null,
modifier = Modifier.fillMaxSize())
Text("Overlay text", modifier = Modifier.align(Alignment.Center))
}
// LazyColumn — scrollable list (RecyclerView equivalent)
LazyColumn(contentPadding = PaddingValues(16.dp)) {
items(items, key = { it.id }) { item ->
ItemCard(item = item, onClick = { onItemClick(item) })
}
item { Spacer(modifier = Modifier.height(80.dp)) } // bottom padding
}Modifier
// Modifier chains — order matters
Modifier
.fillMaxSize() // fill parent
.fillMaxWidth().height(56.dp) // fixed height, full width
.size(48.dp) // square
.padding(horizontal = 16.dp, vertical = 8.dp)
.background(MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(8.dp))
.clip(RoundedCornerShape(8.dp)) // clip to shape
.border(1.dp, Color.Gray, RoundedCornerShape(8.dp))
.clickable { onClick() }
.clickable(onClick = onClick, role = Role.Button)
.semantics { contentDescription = "Increment counter" } // accessibility
.testTag("counter_button") // UI testing
.weight(1f) // in Row/Column: take remaining spaceCommon Components
// Text field
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("Email") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
isError = !text.contains("@"),
supportingText = { if (!text.contains("@")) Text("Invalid email") }
)
// Image loading (Coil)
// implementation("io.coil-kt:coil-compose:2.6.0")
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = "Profile photo",
modifier = Modifier.size(40.dp).clip(CircleShape),
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.placeholder),
error = painterResource(R.drawable.error),
)
// Scaffold — standard screen layout
Scaffold(
topBar = {
TopAppBar(
title = { Text("My App") },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = { /* add item */ }) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
) { paddingValues ->
Content(modifier = Modifier.padding(paddingValues))
}