Jetpack Compose is Google’s modern toolkit for building native Android UI. It simplifies and accelerates UI development with less code, powerful tools, and intuitive Kotlin APIs. This comprehensive guide covers everything you need to know.
What is Jetpack Compose?
Jetpack Compose is Android’s modern, declarative UI toolkit that replaces the traditional XML-based views.
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview
@Composable
fun PreviewGreeting() {
Greeting("Android")
}
Key Features
- Declarative - Describe what UI should look like
-
- Kotlin-first - Written in and for Kotlin
-
- Composable - Functions that build UI
-
- State-driven - UI updates automatically
-
- Material Design - Built-in Material 3 support
-
- Less Code - Up to 50% less code than XML
Basic Composables
Text and Button
@Composable
fun SimpleScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Welcome to Compose!",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { /* Handle click */ },
modifier = Modifier.fillMaxWidth()
) {
Text("Click Me")
}
}
}
TextField
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { /* Login */ },
modifier = Modifier.fillMaxWidth()
) {
Text("Login")
}
}
}
Layouts
Column, Row, Box
@Composable
fun LayoutExamples() {
// Vertical layout
Column(
modifier = Modifier.padding(8.dp)
) {
Text("Item 1")
Text("Item 2")
}
// Horizontal layout
Row(
modifier = Modifier.padding(8.dp)
) {
Text("Left")
Spacer(modifier = Modifier.width(8.dp))
Text("Right")
}
// Stack/overlay
Box(
modifier = Modifier.fillMaxSize()
) {
Text("Background")
Text(
text = "Foreground",
modifier = Modifier.align(Alignment.Center)
)
}
}
Lazy Lists
@Composable
fun LazyListExample() {
val items = (1..100).toList()
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(items) { item ->
Text(
text = "Item $item",
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
}
@Composable
fun LazyRowExample() {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(10) { index ->
Card(
modifier = Modifier.size(100.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Item $index")
}
}
}
}
}
State Management
remember and mutableStateOf
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Count: $count",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(
onClick = { count++ }
) {
Text("Increment")
}
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = { count-- },
enabled = count > 0
) {
Text("Decrement")
}
}
}
}
State Hoisting
@Composable
fun UserCard(
name: String,
email: String,
onEditClick: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = name,
style = MaterialTheme.typography.titleMedium
)
Text(
text = email,
style = MaterialTheme.typography.bodyMedium
)
Button(
onClick = onEditClick,
modifier = Modifier.align(Alignment.End)
) {
Text("Edit")
}
}
}
}
ViewModel Integration
// ViewModel
class MainViewModel : ViewModel() {
private val _uiState = mutableStateOf(UiState())
val uiState: State<UiState> = _uiState
fun loadData() {
_uiState.value = _uiState.value.copy(isLoading = true)
// Load data...
_uiState.value = _uiState.value.copy(
isLoading = false,
data = loadedData
)
}
}
data class UiState(
val isLoading: Boolean = false,
val data: List<String> = emptyList(),
val error: String? = null
)
// Composable
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
when {
uiState.isLoading -> CircularProgressIndicator()
uiState.error != null -> Text(uiState.error!!)
else -> LazyColumn {
items(uiState.data) { item ->
Text(item)
}
}
}
}
Navigation
Compose Navigation
// Navigation setup
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable("detail/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
}
}
// Home Screen
@Composable
fun HomeScreen(
onNavigateToDetail: (String) -> Unit
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Home Screen")
Button(
onClick = { onNavigateToDetail("123") }
) {
Text("Go to Detail")
}
}
}
Material Design
Material 3 Theme
@Composable
fun MyTheme(
content: @Composable () -> Unit
) {
MaterialTheme(
colorScheme = MaterialTheme.colorScheme.copy(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
background = Color.White
),
typography = MaterialTheme.typography,
content = content
)
}
Components
@Composable
fun MaterialComponents() {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Button
Button(onClick = {}) { Text("Button") }
// Outlined Button
OutlinedButton(onClick = {}) { Text("Outlined") }
// Text Button
TextButton(onClick = {}) { Text("Text") }
// FAB
FloatingActionButton(
onClick = {}
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
// Card
Card(
modifier = Modifier.fillMaxWidth()
) {
Text("Card Content")
}
// Chip
FilterChip(
selected = true,
onClick = {},
label = { Text("Chip") }
)
// Bottom Navigation
BottomNavigation {
BottomNavigationItem(
selected = true,
onClick = {},
icon = { Icon(Icons.Default.Home, null) },
label = { Text("Home") }
)
}
}
}
Side Effects
LaunchedEffect
@Composable
fun TimerScreen() {
var time by remember { mutableStateOf(0L) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
time++
}
}
Text("Time: $time seconds")
}
rememberCoroutineScope
@Composable
fun AsyncButton() {
val scope = rememberCoroutineScope()
Button(
onClick = {
scope.launch {
// Async operation
val result = withContext(Dispatchers.IO) {
fetchData()
}
}
}
) {
Text("Load Data")
}
}
produceState
@Composable
fun UserAvatar(userId: String): State<Bitmap?> {
return produceState<Bitmap?>(null, userId) {
value = loadImage(userId)
}
}
Animations
Animated Visibility
@Composable
fun AnimatedContentExample() {
var visible by remember { mutableStateOf(true) }
Column {
Button(onClick = { visible = !visible }) {
Text("Toggle")
}
AnimatedVisibility(
visible = visible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Text("This animates!")
}
}
}
Animate Content Size
@Composable
fun AnimateSize() {
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.animateContentSize()
.size(if (expanded) 200.dp else 100.dp)
.background(Color.Blue)
.clickable { expanded = !expanded }
)
}
Dependency Injection
Hilt with Compose
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: MyRepository
) : ViewModel() {
// ViewModel code
}
@Composable
fun MyScreen(
viewModel: MyViewModel = hiltViewModel()
) {
// Compose code
}
Testing
Compose Testing
@Composable
fun CounterDisplay(count: Int, onIncrement: () -> Unit) {
Column {
Text("Count: $count")
Button(onClick = onIncrement) {
Text("Increment")
}
}
}
@Test
fun counterIncrement() {
composeTestRule.setContent {
var count by mutableStateOf(0)
CounterDisplay(
count = count,
onIncrement = { count++ }
)
}
composeTestRule.onNodeWithText("Increment").performClick()
composeTestRule.onNodeWithText("Count: 1").assertExists()
}
External Resources
Conclusion
Jetpack Compose represents the future of Android UI development. Key points:
- Declarative syntax simplifies UI code
- Compose functions handle state changes automatically
- Built-in Material 3 support
- Works seamlessly with ViewModel and Hilt
- Excellent tooling support in Android Studio
For new Android projects, Jetpack Compose is the recommended approach for building modern UIs.
Comments