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.

godot-master
Consolidated expert library for professional Godot 4.x game and application development. Orchestrates 94 specialized blueprints through architectural workflows, anti-pattern catalogs, performance budgets, and Server API patterns. Use when: (1) starting a new Godot project, (2) designing game or app architecture, (3) building entity/component systems, (4) debugging performance or physics issues, (5) choosing between 2D/3D approaches, (6) implementing multiplayer, (7) optimizing draw calls or script time, (8) porting between platforms. Primary entry point for ALL Godot development tasks.

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.

design-is
Audit designs against Dieter Rams' ten principles, then auto-handoff to /make-plan for new, refine, or redesign outcomes.