Android Jetpack Compose¶
Android's modern declarative UI toolkit: composable functions, state management, layout composables, Modifier chains, lists with LazyColumn, navigation, and Material 3 theming.
Key Facts¶
- Declarative: describe WHAT UI looks like, framework handles updates. No XML, no
findViewById @Composableannotation marks UI functions; UI recomposes when state changesremember { mutableStateOf(value) }survives recompositions but NOT config changesrememberSaveable { }survives config changes (serializes to Bundle)- State hoisting: move state UP to caller for reusability and testability
- Modifier order matters:
.padding().background()applies padding OUTSIDE background LazyColumn= RecyclerView equivalent with efficient recycling
Patterns¶
Basic Composable¶
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { MyAppTheme { Greeting("Android") } }
}
}
Layout Composables¶
// Column (vertical), Row (horizontal), Box (overlapping)
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("First")
Text("Second")
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { Text("Left"); Text("Right") }
State Management¶
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) { Text("Increment") }
}
}
State Hoisting¶
// Stateless composable (preferred)
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Column {
Text("Count: $count")
Button(onClick = onIncrement) { Text("+") }
}
}
// Stateful parent
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}
ViewModel Integration¶
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
when (uiState) {
is UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> UserList(uiState.data)
is UiState.Error -> ErrorMessage(uiState.message)
}
}
LazyColumn (Efficient List)¶
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(users, key = { it.id }) { user -> UserCard(user) }
}
// Grid
LazyVerticalGrid(columns = GridCells.Fixed(2)) {
items(products) { ProductCard(it) }
}
Common Components¶
// Input
var text by remember { mutableStateOf("") }
OutlinedTextField(value = text, onValueChange = { text = it },
label = { Text("Email") })
// Card
Card(modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(4.dp)) {
Column(Modifier.padding(16.dp)) {
Text(user.name, style = MaterialTheme.typography.titleMedium)
}
}
// Image with Coil
AsyncImage(model = user.avatarUrl, contentDescription = "Avatar",
modifier = Modifier.size(48.dp).clip(CircleShape))
Navigation¶
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = "users") {
composable("users") {
UserListScreen(onUserClick = { id ->
navController.navigate("user/$id")
})
}
composable("user/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.IntType })) {
UserDetailScreen(it.arguments?.getInt("userId") ?: 0)
}
}
}
Theming¶
@Composable
fun MyAppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = if (darkTheme) darkColorScheme() else lightColorScheme(),
typography = Typography,
content = content
)
}
Gotchas¶
remembersurvives recomposition but NOT config changes (rotation) - userememberSaveablefor important state- Modifier order matters:
.padding(8.dp).background(Red)vs.background(Red).padding(8.dp)look different - Composables must be idempotent and free of side effects - use
LaunchedEffectfor side effects LazyColumnitems should have stablekeyfor efficient diffing- Don't call ViewModel directly in composable body - side effects in
LaunchedEffector event handlers
See Also¶
- android architecture mvvm - ViewModel and state management patterns
- kotlin language features - Kotlin features used extensively in Compose
- android recyclerview - View-system equivalent (RecyclerView)