PluginBench
Skill
Review
Audit score 70

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
Prerequisites
  • Android Studio with Kotlin support
  • Jetpack Compose dependencies in build.gradle
  • Kotlin 1.8 or later
Claude Code
Cursor
Windsurf
Cline

How to use android-jetpack-compose

  1. 1.Define state using remember { mutableStateOf() } or connect to a ViewModel
  2. 2.Create composable functions with @Composable annotation and Modifier parameters
  3. 3.Hoist state upward to parent composables for reusability
  4. 4.Use LaunchedEffect or SideEffect for data loading and lifecycle management
  5. 5.Apply Material 3 theming via MaterialTheme wrapper around your content
  6. 6.Build lists with LazyColumn/LazyVerticalGrid and stable keys for efficiency

Use cases

Good for
  • 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
Who it's for
  • Android developers building native UIs
  • Teams adopting declarative UI patterns
  • Developers migrating from XML layouts to Compose

android-jetpack-compose FAQ

When should I use remember vs rememberSaveable?

Use remember for state that survives recomposition within a session. Use rememberSaveable when you need state to survive configuration changes like screen rotation.

How do I avoid unnecessary recompositions?

Use stable keys in LazyColumn/LazyVerticalGrid items, apply derivedStateOf for computed state, and hoist state to the appropriate level so only affected composables recompose.

Should I manage state in composables or ViewModels?

Use composables for UI-only state (like text field focus). Use ViewModels with StateFlow for business logic and data that survives configuration changes.

What is state hoisting and why does it matter?

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.

How do I handle side effects like API calls?

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.

FA

fastapi-async-patterns

thebushidocollective/han

Use when FastAPI async patterns for building high-performance APIs. Use when handling concurrent requests and async operations.

895 installs
ST

storybook-story-writing

thebushidocollective/han

Use when creating or modifying Storybook stories for components. Ensures stories follow CSF3 format, properly showcase component variations, and build successfully.

601 installs
GOgodot-master logo

godot-master

thedivergentai/gd-agentic-skills

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.

1.3k installs
BAbabysit logo

babysit

thedotmack/claude-mem

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

2.1k installs
CLclaude-code-plugin-release logo

claude-code-plugin-release

thedotmack/claude-mem

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

3.3k installs
DEdesign-is logo

design-is

thedotmack/claude-mem

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

1.4k installsAudited