# Jetpack Compose Component Library ## Lists and Collections ### Basic LazyColumn ```kotlin @Composable fun ItemList( items: List, onItemClick: (Item) -> Unit, modifier: Modifier = Modifier ) { LazyColumn( modifier = modifier.fillMaxSize(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { items( items = items, key = { it.id } ) { item -> ItemRow( item = item, onClick = { onItemClick(item) } ) } } } ``` ### Pull to Refresh ```kotlin @OptIn(ExperimentalMaterial3Api::class) @Composable fun RefreshableList( items: List, isRefreshing: Boolean, onRefresh: () -> Unit ) { val pullToRefreshState = rememberPullToRefreshState() PullToRefreshBox( state = pullToRefreshState, isRefreshing = isRefreshing, onRefresh = onRefresh ) { LazyColumn( modifier = Modifier.fillMaxSize() ) { items(items) { item -> ItemRow(item = item) } } } } ``` ### Swipe to Dismiss ```kotlin @OptIn(ExperimentalMaterial3Api::class) @Composable fun SwipeableItem( item: Item, onDelete: () -> Unit ) { val dismissState = rememberSwipeToDismissBoxState( confirmValueChange = { value -> if (value == SwipeToDismissBoxValue.EndToStart) { onDelete() true } else { false } } ) SwipeToDismissBox( state = dismissState, backgroundContent = { Box( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.errorContainer) .padding(horizontal = 20.dp), contentAlignment = Alignment.CenterEnd ) { Icon( Icons.Default.Delete, contentDescription = "Delete", tint = MaterialTheme.colorScheme.onErrorContainer ) } } ) { ItemRow(item = item) } } ``` ### Sticky Headers ```kotlin @OptIn(ExperimentalFoundationApi::class) @Composable fun GroupedList( groups: Map> ) { LazyColumn { groups.forEach { (header, items) -> stickyHeader { Surface( modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surfaceVariant ) { Text( text = header, modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } items(items, key = { it.id }) { item -> ItemRow(item = item) } } } } ``` ## Forms and Input ### Text Fields ```kotlin @Composable fun LoginForm( onLogin: (email: String, password: String) -> Unit ) { var email by rememberSaveable { mutableStateOf("") } var password by rememberSaveable { mutableStateOf("") } var passwordVisible by rememberSaveable { mutableStateOf(false) } var emailError by remember { mutableStateOf(null) } Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { OutlinedTextField( value = email, onValueChange = { email = it emailError = if (it.isValidEmail()) null else "Invalid email" }, label = { Text("Email") }, placeholder = { Text("name@example.com") }, leadingIcon = { Icon(Icons.Default.Email, null) }, isError = emailError != null, supportingText = emailError?.let { { Text(it) } }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email, imeAction = ImeAction.Next ), singleLine = true, modifier = Modifier.fillMaxWidth() ) OutlinedTextField( value = password, onValueChange = { password = it }, label = { Text("Password") }, leadingIcon = { Icon(Icons.Default.Lock, null) }, trailingIcon = { IconButton(onClick = { passwordVisible = !passwordVisible }) { Icon( if (passwordVisible) Icons.Default.VisibilityOff else Icons.Default.Visibility, contentDescription = "Toggle password visibility" ) } }, visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, imeAction = ImeAction.Done ), singleLine = true, modifier = Modifier.fillMaxWidth() ) Button( onClick = { onLogin(email, password) }, modifier = Modifier.fillMaxWidth(), enabled = email.isNotEmpty() && password.isNotEmpty() && emailError == null ) { Text("Sign In") } } } ``` ### Search Bar ```kotlin @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchableScreen( items: List, onItemClick: (Item) -> Unit ) { var query by rememberSaveable { mutableStateOf("") } var expanded by rememberSaveable { mutableStateOf(false) } val filteredItems = remember(query, items) { if (query.isEmpty()) items else items.filter { it.name.contains(query, ignoreCase = true) } } SearchBar( query = query, onQueryChange = { query = it }, onSearch = { expanded = false }, active = expanded, onActiveChange = { expanded = it }, placeholder = { Text("Search items") }, leadingIcon = { Icon(Icons.Default.Search, null) }, trailingIcon = { if (query.isNotEmpty()) { IconButton(onClick = { query = "" }) { Icon(Icons.Default.Clear, "Clear search") } } }, modifier = Modifier .fillMaxWidth() .padding(horizontal = if (expanded) 0.dp else 16.dp) ) { LazyColumn( modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(16.dp) ) { items(filteredItems) { item -> ListItem( headlineContent = { Text(item.name) }, supportingContent = { Text(item.description) }, modifier = Modifier.clickable { onItemClick(item) expanded = false } ) } } } } ``` ### Selection Controls ```kotlin @Composable fun SettingsScreen() { var notificationsEnabled by rememberSaveable { mutableStateOf(true) } var selectedOption by rememberSaveable { mutableStateOf(0) } var expandedDropdown by remember { mutableStateOf(false) } var selectedLanguage by rememberSaveable { mutableStateOf("English") } val languages = listOf("English", "Spanish", "French", "German") Column { // Switch ListItem( headlineContent = { Text("Enable Notifications") }, supportingContent = { Text("Receive push notifications") }, trailingContent = { Switch( checked = notificationsEnabled, onCheckedChange = { notificationsEnabled = it } ) } ) HorizontalDivider() // Radio buttons Column { Text( "Theme", modifier = Modifier.padding(16.dp), style = MaterialTheme.typography.titleSmall ) listOf("System", "Light", "Dark").forEachIndexed { index, option -> Row( modifier = Modifier .fillMaxWidth() .selectable( selected = selectedOption == index, onClick = { selectedOption = index }, role = Role.RadioButton ) .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { RadioButton( selected = selectedOption == index, onClick = null ) Spacer(Modifier.width(16.dp)) Text(option) } } } HorizontalDivider() // Dropdown ExposedDropdownMenuBox( expanded = expandedDropdown, onExpandedChange = { expandedDropdown = it }, modifier = Modifier.padding(16.dp) ) { OutlinedTextField( value = selectedLanguage, onValueChange = {}, readOnly = true, label = { Text("Language") }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedDropdown) }, modifier = Modifier .fillMaxWidth() .menuAnchor() ) ExposedDropdownMenu( expanded = expandedDropdown, onDismissRequest = { expandedDropdown = false } ) { languages.forEach { language -> DropdownMenuItem( text = { Text(language) }, onClick = { selectedLanguage = language expandedDropdown = false }, contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding ) } } } } } ``` ## Dialogs and Bottom Sheets ### Alert Dialog ```kotlin @Composable fun DeleteConfirmationDialog( itemName: String, onConfirm: () -> Unit, onDismiss: () -> Unit ) { AlertDialog( onDismissRequest = onDismiss, icon = { Icon( Icons.Default.Warning, contentDescription = null, tint = MaterialTheme.colorScheme.error ) }, title = { Text("Delete Item?") }, text = { Text("Are you sure you want to delete \"$itemName\"? This action cannot be undone.") }, confirmButton = { TextButton( onClick = onConfirm, colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.error ) ) { Text("Delete") } }, dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } } ) } ``` ### Modal Bottom Sheet ```kotlin @OptIn(ExperimentalMaterial3Api::class) @Composable fun OptionsBottomSheet( onDismiss: () -> Unit, onOptionSelected: (String) -> Unit ) { val sheetState = rememberModalBottomSheetState() ModalBottomSheet( onDismissRequest = onDismiss, sheetState = sheetState, dragHandle = { BottomSheetDefaults.DragHandle() } ) { Column( modifier = Modifier .fillMaxWidth() .padding(bottom = 32.dp) ) { Text( "Options", modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), style = MaterialTheme.typography.titleMedium ) listOf( Triple(Icons.Default.Share, "Share", "share"), Triple(Icons.Default.Edit, "Edit", "edit"), Triple(Icons.Default.FileCopy, "Duplicate", "duplicate"), Triple(Icons.Default.Delete, "Delete", "delete") ).forEach { (icon, label, action) -> ListItem( headlineContent = { Text(label) }, leadingContent = { Icon( icon, contentDescription = null, tint = if (action == "delete") MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant ) }, modifier = Modifier.clickable { onOptionSelected(action) } ) } } } } ``` ### Date and Time Pickers ```kotlin @OptIn(ExperimentalMaterial3Api::class) @Composable fun DateTimePickerExample() { var showDatePicker by remember { mutableStateOf(false) } var showTimePicker by remember { mutableStateOf(false) } val datePickerState = rememberDatePickerState() val timePickerState = rememberTimePickerState() Column(modifier = Modifier.padding(16.dp)) { OutlinedButton(onClick = { showDatePicker = true }) { Icon(Icons.Default.CalendarToday, null) Spacer(Modifier.width(8.dp)) Text( datePickerState.selectedDateMillis?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()) .format(Date(it)) } ?: "Select Date" ) } Spacer(Modifier.height(16.dp)) OutlinedButton(onClick = { showTimePicker = true }) { Icon(Icons.Default.Schedule, null) Spacer(Modifier.width(8.dp)) Text( String.format("%02d:%02d", timePickerState.hour, timePickerState.minute) ) } } if (showDatePicker) { DatePickerDialog( onDismissRequest = { showDatePicker = false }, confirmButton = { TextButton(onClick = { showDatePicker = false }) { Text("OK") } }, dismissButton = { TextButton(onClick = { showDatePicker = false }) { Text("Cancel") } } ) { DatePicker(state = datePickerState) } } if (showTimePicker) { AlertDialog( onDismissRequest = { showTimePicker = false }, confirmButton = { TextButton(onClick = { showTimePicker = false }) { Text("OK") } }, dismissButton = { TextButton(onClick = { showTimePicker = false }) { Text("Cancel") } }, text = { TimePicker(state = timePickerState) } ) } } ``` ## Loading States ### Progress Indicators ```kotlin @Composable fun LoadingStates() { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(24.dp) ) { // Indeterminate circular CircularProgressIndicator() // Determinate circular CircularProgressIndicator( progress = { 0.7f }, strokeWidth = 4.dp ) // Indeterminate linear LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) // Determinate linear LinearProgressIndicator( progress = { 0.7f }, modifier = Modifier.fillMaxWidth() ) } } ``` ### Skeleton Loading ```kotlin @Composable fun SkeletonLoader( modifier: Modifier = Modifier ) { val infiniteTransition = rememberInfiniteTransition(label = "skeleton") val alpha by infiniteTransition.animateFloat( initialValue = 0.3f, targetValue = 0.7f, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "alpha" ) Column( modifier = modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { repeat(5) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) ) { Box( modifier = Modifier .size(48.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha)) ) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Box( modifier = Modifier .height(16.dp) .fillMaxWidth(0.7f) .clip(RoundedCornerShape(4.dp)) .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha)) ) Box( modifier = Modifier .height(12.dp) .fillMaxWidth(0.5f) .clip(RoundedCornerShape(4.dp)) .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = alpha)) ) } } } } } ``` ### Content Loading Pattern ```kotlin @Composable fun AsyncContent( state: AsyncState, onRetry: () -> Unit, content: @Composable (T) -> Unit ) { when (state) { is AsyncState.Loading -> { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } is AsyncState.Success -> { content(state.data) } is AsyncState.Error -> { Column( modifier = Modifier .fillMaxSize() .padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Icon( Icons.Default.Error, contentDescription = null, modifier = Modifier.size(64.dp), tint = MaterialTheme.colorScheme.error ) Spacer(Modifier.height(16.dp)) Text( "Something went wrong", style = MaterialTheme.typography.titleMedium ) Spacer(Modifier.height(8.dp)) Text( state.message, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center ) Spacer(Modifier.height(24.dp)) Button(onClick = onRetry) { Text("Try Again") } } } } } sealed class AsyncState { object Loading : AsyncState() data class Success(val data: T) : AsyncState() data class Error(val message: String) : AsyncState() } ``` ## Animations ### Animated Visibility ```kotlin @Composable fun ExpandableCard( title: String, content: String ) { var expanded by rememberSaveable { mutableStateOf(false) } Card( modifier = Modifier.fillMaxWidth() ) { Column( modifier = Modifier .clickable { expanded = !expanded } .padding(16.dp) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text(title, style = MaterialTheme.typography.titleMedium) Icon( if (expanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, contentDescription = if (expanded) "Collapse" else "Expand" ) } AnimatedVisibility( visible = expanded, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut() ) { Text( text = content, modifier = Modifier.padding(top = 12.dp), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } } } ``` ### Animated Content ```kotlin @Composable fun AnimatedCounter(count: Int) { AnimatedContent( targetState = count, transitionSpec = { if (targetState > initialState) { slideInVertically { -it } + fadeIn() togetherWith slideOutVertically { it } + fadeOut() } else { slideInVertically { it } + fadeIn() togetherWith slideOutVertically { -it } + fadeOut() }.using(SizeTransform(clip = false)) }, label = "counter" ) { targetCount -> Text( text = "$targetCount", style = MaterialTheme.typography.displayMedium ) } } ``` ### Gesture-Based Animation ```kotlin @Composable fun SwipeableCard( onSwipeLeft: () -> Unit, onSwipeRight: () -> Unit, content: @Composable () -> Unit ) { var offsetX by remember { mutableFloatStateOf(0f) } val animatedOffset by animateFloatAsState( targetValue = offsetX, animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy), label = "offset" ) Card( modifier = Modifier .fillMaxWidth() .offset { IntOffset(animatedOffset.roundToInt(), 0) } .pointerInput(Unit) { detectHorizontalDragGestures( onDragEnd = { when { offsetX > 200f -> { onSwipeRight() offsetX = 0f } offsetX < -200f -> { onSwipeLeft() offsetX = 0f } else -> offsetX = 0f } }, onHorizontalDrag = { _, dragAmount -> offsetX += dragAmount } ) } ) { content() } } ```