Skip to main content
โšก Calmops

Jetpack Compose: Modern Android UI Toolkit

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)
            }
        }
    }
}

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