$npx -y skills add hanamizuki/solopreneur --skill jetpack-composeUse when building Android UIs with Jetpack Compose, managing state with remember/mutableStateOf, or implementing declarative UI patterns.
| 1 | # Android - Jetpack Compose |
| 2 | |
| 3 | Modern declarative UI toolkit for building native Android interfaces. |
| 4 | |
| 5 | ## Key Concepts |
| 6 | |
| 7 | ### State Management |
| 8 | |
| 9 | Compose provides several ways to manage state: |
| 10 | |
| 11 | - **remember**: Survives recomposition |
| 12 | - **rememberSaveable**: Survives configuration changes |
| 13 | - **mutableStateOf**: Creates observable state |
| 14 | - **derivedStateOf**: Computed state that updates when dependencies change |
| 15 | |
| 16 | ```kotlin |
| 17 | @Composable |
| 18 | fun Counter() { |
| 19 | var count by remember { mutableStateOf(0) } |
| 20 | |
| 21 | Column { |
| 22 | Text("Count: $count") |
| 23 | Button(onClick = { count++ }) { |
| 24 | Text("Increment") |
| 25 | } |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | // With saveable for configuration changes |
| 30 | @Composable |
| 31 | fun SearchField() { |
| 32 | var query by rememberSaveable { mutableStateOf("") } |
| 33 | |
| 34 | TextField( |
| 35 | value = query, |
| 36 | onValueChange = { query = it }, |
| 37 | placeholder = { Text("Search...") } |
| 38 | ) |
| 39 | } |
| 40 | ``` |
| 41 | |
| 42 | ### State Hoisting |
| 43 | |
| 44 | Lift state up to make composables stateless and reusable: |
| 45 | |
| 46 | ```kotlin |
| 47 | // Stateless composable |
| 48 | @Composable |
| 49 | fun NameInput( |
| 50 | name: String, |
| 51 | onNameChange: (String) -> Unit, |
| 52 | modifier: Modifier = Modifier |
| 53 | ) { |
| 54 | TextField( |
| 55 | value = name, |
| 56 | onValueChange = onNameChange, |
| 57 | label = { Text("Name") }, |
| 58 | modifier = modifier |
| 59 | ) |
| 60 | } |
| 61 | |
| 62 | // Stateful parent |
| 63 | @Composable |
| 64 | fun UserForm() { |
| 65 | var name by remember { mutableStateOf("") } |
| 66 | |
| 67 | NameInput( |
| 68 | name = name, |
| 69 | onNameChange = { name = it } |
| 70 | ) |
| 71 | } |
| 72 | ``` |
| 73 | |
| 74 | ### ViewModel Integration |
| 75 | |
| 76 | ```kotlin |
| 77 | class UserViewModel : ViewModel() { |
| 78 | private val _uiState = MutableStateFlow(UserUiState()) |
| 79 | val uiState: StateFlow<UserUiState> = _uiState.asStateFlow() |
| 80 | |
| 81 | fun updateName(name: String) { |
| 82 | _uiState.update { it.copy(name = name) } |
| 83 | } |
| 84 | |
| 85 | fun saveUser() { |
| 86 | viewModelScope.launch { |
| 87 | _uiState.update { it.copy(isLoading = true) } |
| 88 | try { |
| 89 | userRepository.save(_uiState.value.toUser()) |
| 90 | _uiState.update { it.copy(isLoading = false, isSaved = true) } |
| 91 | } catch (e: Exception) { |
| 92 | _uiState.update { it.copy(isLoading = false, error = e.message) } |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | @Composable |
| 99 | fun UserScreen(viewModel: UserViewModel = viewModel()) { |
| 100 | val uiState by viewModel.uiState.collectAsStateWithLifecycle() |
| 101 | |
| 102 | UserContent( |
| 103 | uiState = uiState, |
| 104 | onNameChange = viewModel::updateName, |
| 105 | onSave = viewModel::saveUser |
| 106 | ) |
| 107 | } |
| 108 | ``` |
| 109 | |
| 110 | ## Best Practices |
| 111 | |
| 112 | ### Composable Function Guidelines |
| 113 | |
| 114 | ```kotlin |
| 115 | // Use Modifier as first optional parameter |
| 116 | @Composable |
| 117 | fun CustomCard( |
| 118 | title: String, |
| 119 | modifier: Modifier = Modifier, |
| 120 | onClick: () -> Unit = {} |
| 121 | ) { |
| 122 | Card( |
| 123 | modifier = modifier.clickable(onClick = onClick) |
| 124 | ) { |
| 125 | Text( |
| 126 | text = title, |
| 127 | modifier = Modifier.padding(16.dp) |
| 128 | ) |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // Use slot APIs for flexible content |
| 133 | @Composable |
| 134 | fun CustomScaffold( |
| 135 | topBar: @Composable () -> Unit = {}, |
| 136 | bottomBar: @Composable () -> Unit = {}, |
| 137 | content: @Composable (PaddingValues) -> Unit |
| 138 | ) { |
| 139 | Scaffold( |
| 140 | topBar = topBar, |
| 141 | bottomBar = bottomBar, |
| 142 | content = content |
| 143 | ) |
| 144 | } |
| 145 | ``` |
| 146 | |
| 147 | ### Efficient Recomposition |
| 148 | |
| 149 | ```kotlin |
| 150 | // Use keys for list items |
| 151 | @Composable |
| 152 | fun UserList(users: List<User>) { |
| 153 | LazyColumn { |
| 154 | items( |
| 155 | items = users, |
| 156 | key = { it.id } // Stable key for efficient updates |
| 157 | ) { user -> |
| 158 | UserItem(user) |
| 159 | } |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | // Use derivedStateOf for expensive computations |
| 164 | @Composable |
| 165 | fun FilteredList(items: List<Item>, query: String) { |
| 166 | val filteredItems by remember(items, query) { |
| 167 | derivedStateOf { |
| 168 | items.filter { it.name.contains(query, ignoreCase = true) } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | LazyColumn { |
| 173 | items(filteredItems) { item -> |
| 174 | ItemRow(item) |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | ``` |
| 179 | |
| 180 | ### Side Effects |
| 181 | |
| 182 | ```kotlin |
| 183 | // LaunchedEffect for coroutine-based side effects |
| 184 | @Composable |
| 185 | fun UserProfile(userId: String, viewModel: UserViewModel) { |
| 186 | LaunchedEffect(userId) { |
| 187 | viewModel.loadUser(userId) |
| 188 | } |
| 189 | |
| 190 | // UI content |
| 191 | } |
| 192 | |
| 193 | // DisposableEffect for cleanup |
| 194 | @Composable |
| 195 | fun LifecycleAwareComponent(lifecycle: Lifecycle) { |
| 196 | DisposableEffect(lifecycle) { |
| 197 | val observer = LifecycleEventObserver { _, event -> |
| 198 | // Handle lifecycle events |
| 199 | } |
| 200 | lifecycle.addObserver(observer) |
| 201 | |
| 202 | onDispose { |
| 203 | lifecycle.removeObserver(observer) |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // SideEffect for non-suspend side effects |
| 209 | @Composable |
| 210 | fun AnalyticsScreen(screenName: String) { |
| 211 | SideEffect { |
| 212 | analytics.logScreenView(screenName) |
| 213 | } |
| 214 | } |
| 215 | ``` |
| 216 | |
| 217 | ## Common Patterns |
| 218 | |
| 219 | ### Navigation with Navigation Compose |
| 220 | |
| 221 | ```kotlin |
| 222 | @Composable |
| 223 | fun AppNavigation( |