Building UIs with Jetpack Compose
1. Creating Composable Functions
| Property | Description | Example |
|---|---|---|
| @Composable | Annotation indicating a function is a composable | @Composable fun Greeting(name: String) { ... } |
| function signature | Composable functions can take parameters like regular functions | @Composable fun UserCard(user: User) { ... } |
| state management | Use mutableStateOf to manage UI state |
var count by remember { mutableStateOf(0) } |
Example: Basic Composable Function
@Composable
fun WelcomeMessage(name: String) {
Text(text = "Hello, $name!")
}
Example: Composable with State
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
2. Using Basic Composables
| Composable | Description | Usage |
|---|---|---|
| Text | Renders text on the screen | Text(text = "Hello, Compose!") |
| Button | Displays a clickable button | Button(onClick = { /* action */ }) { Text("Click me") } |
| Image | Displays an image resource | Image(painter = painterResource(id = R.drawable.icon), contentDescription = null) |
| Box | Layout container for arranging composables | Box { Text("Inside Box") } |
| Column | Arranges composables vertically | Column { Text("Line 1"); Text("Line 2") } |
| Row | Arranges composables horizontally | Row { Text("Left"); Text("Right") } |
Example: Simple Text Display
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
Example: Button with Action
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@Composable
fun ClickableButton() {
Button(onClick = { /* Handle click */ }) {
Text(text = "Press Me")
}
}
@Composable annotation.3. Implementing Layouts
Example: Basic Column and Row
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
@Composable
fun BasicLayout() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(text = "Header")
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Content")
Spacer(modifier = Modifier.height(8.dp))
Text(text = "Footer")
}
}
| Layout | Description | Usage |
|---|---|---|
| Box | Overlapping layout container | Use for layered UIs or overlays |
| Column | Vertical arrangement of composables | Stack elements vertically |
| Row | Horizontal arrangement of composables | Stack elements horizontally |
| LazyColumn | Vertical scrollable list | Efficient for large lists |
| LazyRow | Horizontal scrollable list | Efficient for horizontal lists |
Example: Using Box for Overlays
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun OverlayExample() {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Background",
modifier = Modifier.align(Alignment.Center)
)
Text(
text = "Overlay",
modifier = Modifier
.align(Alignment.Center)
.offset(y = 40.dp)
)
}
}
Example: Nested Layouts
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
@Composable
fun NestedLayouts() {
Column(
modifier = Modifier.fillMaxSize()
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Top Bar")
}
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(text = "Main Content")
}
}
}
4. Managing State
Example: Basic State with mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.unit.dp
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(text = "Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
| Property | Description | Usage |
|---|---|---|
| mutableStateOf(T) | Creates a mutable state holder for a value of type T | var count by remember { mutableStateOf(0) } |
| derivedStateOf { ... } | Creates a state that is derived from other states | val isEven = derivedStateOf { count % 2 == 0 } |
| remember { ... } | Stores state across recompositions | remember { mutableStateOf(0) } |
mutableStateOf is used to create a state variable that will trigger recomposition when changed.Example: State with ViewModel
import androidx.lifecycle.ViewModel
import androidx.compose.runtime.*
class CounterViewModel : ViewModel() {
private val _count = mutableStateOf(0)
val count: Int
get() = _count.value
fun increment() {
_count.value++
}
}
@Composable
fun CounterWithViewModel(viewModel: CounterViewModel) {
val count = viewModel.count
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(text = "Count: $count")
Button(onClick = { viewModel.increment() }) {
Text("Increment")
}
}
}
State Management Best Practices
- Use
rememberto preserve state across recompositions - Prefer
mutableStateOffor simple local state - Use
derivedStateOfto compute derived values efficiently - For complex state, consider using
ViewModelwithLiveDataorStateFlow
5. Using Modifiers
Modifiers in Jetpack Compose are used to customize the appearance and behavior of composables. They allow you to apply transformations like padding, margins, size constraints, and layout adjustments.
| Modifier | Description | Usage Example |
|---|---|---|
| padding() | Applies padding around the composable | |
| size() | Sets the width and height of the composable | |
| background() | Sets the background color or drawable | |
| border() | Adds a border around the composable | |
| clickable() | Makes the composable respond to clicks | |
Example: Combining Modifiers
Box(
modifier = Modifier
.size(100.dp)
.padding(16.dp)
.background(Color.Red)
.border(2.dp, Color.Black)
)
6. Handling User Input
| Component | Description | Usage |
|---|---|---|
| TextField | Displays a text input field for user entry. | TextField(value = text, onValueChange = { text = it }) |
| Button | Triggers an action when clicked. | Button(onClick = { /* action */ }) { Text("Click") } |
| Switch | Allows toggling between two states. | Switch(checked = isSwitched, onCheckedChange = { isSwitched = it }) |
Example: Handling Text Input
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@Composable
fun TextInputExample() {
var text by remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Enter text") }
)
}
}
Example: Handling Button Clicks
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@Composable
fun ButtonClickExample() {
var counter by remember { mutableStateOf(0) }
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Counter: $counter")
Button(onClick = { counter++ }) {
Text("Increment")
}
}
}
7. Using LazyColumn and LazyRow
LazyColumn and LazyRow are composables that efficiently render large lists of items by only creating and laying out the items currently visible on screen. They are essential for building performant UIs in Jetpack Compose.
| Property | Description | Values |
|---|---|---|
| items | List of items to render | Items in a list or array |
| itemContent | Composable to render each item | Composable function |
| horizontalAlignment | Horizontal alignment of items in LazyRow | Start, CenterHorizontally, End |
| verticalAlignment | Vertical alignment of items in LazyColumn | Top, CenterVertically, Bottom |
Example: Basic LazyColumn
val items = (1..100).map { "Item $it" }
LazyColumn {
items(items) { item ->
Text(text = item, modifier = Modifier.padding(16.dp))
}
}
Example: LazyRow with Horizontal Layout
val items = (1..20).map { "Card $it" }
LazyRow {
items(items) { item ->
Card(
modifier = Modifier
.padding(8.dp)
.size(100.dp),
onClick = { /* Handle click */ }
) {
Text(
text = item,
modifier = Modifier
.fillMaxSize()
.align(Alignment.Center)
)
}
}
}
LazyColumn and LazyRow use items to efficiently manage item rendering. For more complex lists, consider using itemsIndexed or item for custom item types.
Key Points
LazyColumnis used for vertical scrolling lists.LazyRowis used for horizontal scrolling lists.- They only compose visible items, improving performance.
- Use
itemsoritemsIndexedto define the list content.
8. Implementing Navigation
Example: Basic Navigation Setup
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@Composable
fun NavigationExample() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(navController)
}
composable("details") {
DetailsScreen()
}
}
}
@Composable
fun HomeScreen(navController: NavHostController) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Home Screen")
}
}
@Composable
fun DetailsScreen() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Details Screen")
}
}
| Navigation Component | Description | Usage |
|---|---|---|
| NavHost | Container for navigation graph | NavHost(navController = navController, startDestination = "home") |
| composable | Defines a screen in the navigation graph | composable("home") { HomeScreen() } |
| NavController | Manages navigation state and actions | val navController = rememberNavController() |
Navigation Workflow
- Define navigation graph using
NavHost - Create composables for each screen
- Use
composableto register screens in the graph - Use
navControllerto navigate between screens
Navigation vs. Activity/Fragment
- Jetpack Navigation: Declarative, type-safe, built-in
- Activity/Fragment: Imperative, more boilerplate, less safe
9. Applying Theming
| Property | Values | Description |
|---|---|---|
| colorScheme | ColorScheme |
Defines the color palette for the app |
| typography | Typography |
Defines text styles and fonts |
| shapes | Shapes |
Defines corner radii for components |
Example: Defining a Custom Theme
val MyTheme = androidx.compose.material3.MaterialTheme(
colorScheme = lightColorScheme(),
typography = Typography(),
shapes = Shapes()
)
Example: Using Theme in UI
@Composable
fun MyScreen() {
MyTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Text(
text = "Hello, World!",
style = MaterialTheme.typography.body1
)
}
}
}
10. Using Side Effects
Side effects in Jetpack Compose refer to operations that have observable effects outside the function's scope, such as I/O operations, network requests, or state mutations. Compose provides several mechanisms to handle side effects safely within the composable function lifecycle.
| Effect Type | Description | Usage Example |
|---|---|---|
| LaunchedEffect | Launches a coroutine inside a composable and cancels it when the composable is disposed. | |
| DisposableEffect | Runs a block of code when the composable is first composed and disposes of resources when it's removed. | |
| RecompositionEffect | Triggers recomposition when a specific condition changes. | |
Example: Using LaunchedEffect to Fetch Data
import androidx.compose.runtime.*
@Composable
fun DataScreen(viewModel: MyViewModel) {
val data by viewModel.data.observeAsState()
LaunchedEffect(Unit) {
viewModel.fetchData()
}
data?.let {
Text(text = it.toString())
}
}
LaunchedEffect is ideal for starting background tasks or observing LiveData/StateFlow.LaunchedEffect inside loops or conditions that might change frequently, as it can lead to excessive recompositions.Best Practices for Side Effects
- Use
LaunchedEffectfor asynchronous operations like API calls. - Use
DisposableEffectfor managing lifecycle-dependent resources like back press handlers. - Use
RecompositionEffectto react to changes in values that require recomposition.
11. Implementing Animation
Example: Basic Fade Animation
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun FadeAnimation() {
var visible by remember { mutableStateOf(true) }
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 300)
)
Box(
modifier = Modifier
.size(100.dp)
.alpha(alpha)
) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(Color.Blue)
}
}
Text(
text = "Click to fade",
modifier = Modifier
.padding(16.dp)
.clickable { visible = !visible }
)
}
| Property | Values | Description |
|---|---|---|
| animateFloatAsState | targetValue: Float |
Animates a float value over time |
| animateIntAsState | targetValue: Int |
Animates an integer value over time |
| animateDpAsState | targetValue: Dp |
Animates a dp value over time |
| tween | durationMillis: Int = 300 |
Easing function for smooth transitions |
Example: Size Change Animation
import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun SizeAnimation() {
var size by remember { mutableStateOf(50.dp) }
val animatedSize by animateDpAsState(
targetValue = if (size == 50.dp) 100.dp else 50.dp,
animationSpec = tween(durationMillis = 500, easing = LinearEasing)
)
Box(
modifier = Modifier
.size(animatedSize)
.background(Color.Green)
)
Button(
onClick = { size = if (size == 50.dp) 100.dp else 50.dp },
modifier = Modifier.padding(16.dp)
) {
Text(text = "Animate Size")
}
}
Example: Rotation Animation
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
@Composable
fun RotationAnimation() {
var rotation by remember { mutableStateOf(0f) }
val animatedRotation by animateFloatAsState(
targetValue = rotation,
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing)
)
Canvas(
modifier = Modifier
.size(100.dp)
.graphicsLayer {
rotationZ = animatedRotation
}
) {
drawCircle(Color.Red)
}
Text(
text = "Rotate",
modifier = Modifier
.padding(16.dp)
.clickable { rotation += 45f }
)
}
12. Interoperating with Views
Jetpack Compose provides mechanisms to integrate with existing View-based UIs, allowing developers to gradually migrate or combine both approaches in the same app.
| Approach | Description | Usage |
|---|---|---|
| View Composables | Wraps View-based components using AndroidView. |
|
| Compose Views | Uses ComposeView to embed Compose UI inside View hierarchies. |
|
Example: Using AndroidView
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
Text("Compose UI")
AndroidView(factory = { context ->
Button(context).apply {
text = "Native View"
}
})
}
}
}
}
AndroidView is useful for integrating legacy UI components or third-party View libraries.