android-jetpack-compose
thebushidocollective/han
Declarative UI toolkit for Android with state management and composable patterns.
What is android-jetpack-compose?
Jetpack Compose is a modern declarative UI framework for building native Android interfaces. Use it when constructing reactive UIs with managed state, implementing Material Design 3 themes, or building lists and navigation flows.
- Manage UI state with remember, mutableStateOf, and rememberSaveable
- Hoist state to create reusable, stateless composables
- Integrate ViewModels with StateFlow for reactive data binding
- Build efficient lists and grids with LazyColumn and LazyVerticalGrid
- Apply Material 3 theming and styling to UI components
- Handle side effects with LaunchedEffect, DisposableEffect, and SideEffect
How to install android-jetpack-compose
npx skills add null --skill android-jetpack-compose- Android Studio with Kotlin support
- Jetpack Compose dependencies in build.gradle
- Kotlin 1.8 or later
How to use android-jetpack-compose
- 1.Define state using remember { mutableStateOf() } or connect to a ViewModel
- 2.Create composable functions with @Composable annotation and Modifier parameters
- 3.Hoist state upward to parent composables for reusability
- 4.Use LaunchedEffect or SideEffect for data loading and lifecycle management
- 5.Apply Material 3 theming via MaterialTheme wrapper around your content
- 6.Build lists with LazyColumn/LazyVerticalGrid and stable keys for efficiency
Use cases
- Building a counter or form with local state management
- Creating a user profile screen that loads data from a ViewModel
- Implementing a searchable product grid with Material Design 3
- Setting up navigation between screens with NavHost and composable routes
- Displaying a contact list with sticky headers grouped by initial letter
- Android developers building native UIs
- Teams adopting declarative UI patterns
- Developers migrating from XML layouts to Compose
android-jetpack-compose FAQ
Use remember for state that survives recomposition within a session. Use rememberSaveable when you need state to survive configuration changes like screen rotation.
Use stable keys in LazyColumn/LazyVerticalGrid items, apply derivedStateOf for computed state, and hoist state to the appropriate level so only affected composables recompose.
Use composables for UI-only state (like text field focus). Use ViewModels with StateFlow for business logic and data that survives configuration changes.
State hoisting means moving state up to a parent composable and passing it down as parameters. This makes composables stateless, reusable, and easier to test.
Use LaunchedEffect with a key parameter to trigger coroutines when dependencies change. Avoid calling suspend functions directly in composition.
Full instructions (SKILL.md)
Source of truth, from thebushidocollective/han.
name: android-jetpack-compose user-invocable: false description: Use when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns. allowed-tools:
- Read
- Write
- Edit
- Bash
- Grep
- Glob
Android - Jetpack Compose
Modern declarative UI toolkit for building native Android interfaces.
Key Concepts
State Management
Compose provides several ways to manage state:
- remember: Survives recomposition
- rememberSaveable: Survives configuration changes
- mutableStateOf: Creates observable state
- derivedStateOf: Computed state that updates when dependencies change
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
// With saveable for configuration changes
@Composable
fun SearchField() {
var query by rememberSaveable { mutableStateOf("") }
TextField(
value = query,
onValueChange = { query = it },
placeholder = { Text("Search...") }
)
}
State Hoisting
Lift state up to make composables stateless and reusable:
// Stateless composable
@Composable
fun NameInput(
name: String,
onNameChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") },
modifier = modifier
)
}
// Stateful parent
@Composable
fun UserForm() {
var name by remember { mutableStateOf("") }
NameInput(
name = name,
onNameChange = { name = it }
)
}
ViewModel Integration
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UserUiState())
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun updateName(name: String) {
_uiState.update { it.copy(name = name) }
}
fun saveUser() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
userRepository.save(_uiState.value.toUser())
_uiState.update { it.copy(isLoading = false, isSaved = true) }
} catch (e: Exception) {
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
}
}
}
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
UserContent(
uiState = uiState,
onNameChange = viewModel::updateName,
onSave = viewModel::saveUser
)
}
Best Practices
Composable Function Guidelines
// Use Modifier as first optional parameter
@Composable
fun CustomCard(
title: String,
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
Card(
modifier = modifier.clickable(onClick = onClick)
) {
Text(
text = title,
modifier = Modifier.padding(16.dp)
)
}
}
// Use slot APIs for flexible content
@Composable
fun CustomScaffold(
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
content: @Composable (PaddingValues) -> Unit
) {
Scaffold(
topBar = topBar,
bottomBar = bottomBar,
content = content
)
}
Efficient Recomposition
// Use keys for list items
@Composable
fun UserList(users: List<User>) {
LazyColumn {
items(
items = users,
key = { it.id } // Stable key for efficient updates
) { user ->
UserItem(user)
}
}
}
// Use derivedStateOf for expensive computations
@Composable
fun FilteredList(items: List<Item>, query: String) {
val filteredItems by remember(items, query) {
derivedStateOf {
items.filter { it.name.contains(query, ignoreCase = true) }
}
}
LazyColumn {
items(filteredItems) { item ->
ItemRow(item)
}
}
}
Side Effects
// LaunchedEffect for coroutine-based side effects
@Composable
fun UserProfile(userId: String, viewModel: UserViewModel) {
LaunchedEffect(userId) {
viewModel.loadUser(userId)
}
// UI content
}
// DisposableEffect for cleanup
@Composable
fun LifecycleAwareComponent(lifecycle: Lifecycle) {
DisposableEffect(lifecycle) {
val observer = LifecycleEventObserver { _, event ->
// Handle lifecycle events
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
}
// SideEffect for non-suspend side effects
@Composable
fun AnalyticsScreen(screenName: String) {
SideEffect {
analytics.logScreenView(screenName)
}
}
Common Patterns
Navigation with Navigation Compose
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = "detail/{itemId}",
arguments = listOf(navArgument("itemId") { type = NavType.StringType })
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId")
DetailScreen(itemId = itemId)
}
}
}
Material 3 Theming
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
else -> lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
)
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
// Using theme values
@Composable
fun ThemedCard() {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Text(
text = "Themed content",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
Lists and Grids
@Composable
fun ProductGrid(products: List<Product>) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(products, key = { it.id }) { product ->
ProductCard(product)
}
}
}
// Sticky headers
@Composable
fun ContactList(contacts: Map<Char, List<Contact>>) {
LazyColumn {
contacts.forEach { (initial, contactsForInitial) ->
stickyHeader {
Text(
text = initial.toString(),
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
style = MaterialTheme.typography.titleMedium
)
}
items(contactsForInitial) { contact ->
ContactItem(contact)
}
}
}
}
Anti-Patterns
Avoid Side Effects in Composition
Bad:
@Composable
fun BadExample(viewModel: ViewModel) {
viewModel.loadData() // Called on every recomposition!
Text("Data loaded")
}
Good:
@Composable
fun GoodExample(viewModel: ViewModel) {
LaunchedEffect(Unit) {
viewModel.loadData()
}
Text("Data loaded")
}
Don't Read State in Remember Block
Bad:
@Composable
fun BadCounter(initial: Int) {
// Won't update when initial changes
var count by remember { mutableStateOf(initial) }
}
Good:
@Composable
fun GoodCounter(initial: Int) {
var count by remember(initial) { mutableStateOf(initial) }
}
Avoid Heavy Computation During Composition
Bad:
@Composable
fun BadList(items: List<Item>) {
// Runs on every recomposition
val sorted = items.sortedBy { it.name }
LazyColumn { /* ... */ }
}
Good:
@Composable
fun GoodList(items: List<Item>) {
val sorted by remember(items) {
derivedStateOf { items.sortedBy { it.name } }
}
LazyColumn { /* ... */ }
}
Related Skills
- android-architecture: MVVM and clean architecture patterns
- android-kotlin-coroutines: Async operations in Compose
Related skills
More from thebushidocollective/han and the wider catalog.
fastapi-async-patterns
Use when FastAPI async patterns for building high-performance APIs. Use when handling concurrent requests and async operations.
storybook-story-writing
Use when creating or modifying Storybook stories for components. Ensures stories follow CSF3 format, properly showcase component variations, and build successfully.

unslop
Remove AI writing patterns from prose—audit or rewrite to humanize AI-generated content.

godot-master
Consolidated expert library for professional Godot 4.7+ game and application development with 92 orchestrated domain skills.

babysit
Monitor pull requests until merge-ready, resolving comments and checks automatically.

claude-code-plugin-release
Automated semantic versioning and release workflow for Claude Code plugins with manifest sync, git tagging, and GitHub releases.