---
description: Kotlin and Android development conventions with Jetpack Compose
globs: "**/*.kt"
---

## Kotlin & Android Conventions

### Naming

- Types/Classes: UpperCamelCase (`UserViewModel`, `HomeScreen`)
- Functions: lowerCamelCase (`loadUserData()`)
- Composables: UpperCamelCase (`@Composable fun UserCard()`)
- Constants: SCREAMING_SNAKE_CASE (`const val MAX_RETRY = 3`)
- Packages: all lowercase (`com.example.feature.home`)

### Jetpack Compose (2024+ standards)

- State: `MutableStateFlow` in ViewModel, `collectAsStateWithLifecycle()` in UI
- Navigation: Type-safe with `@Serializable` route objects (Navigation 2.8+)
- Side effects: `LaunchedEffect`, `DisposableEffect`, `SideEffect`  -  never in body
- Stability: Mark data classes `@Immutable` or `@Stable` for recomposition optimization
- Remember: Always `remember {}` for expensive computations
- ViewModel: Never pass ViewModel to child composables  -  pass state + lambdas

### Architecture

- MVVM/MVI with `ViewModel` + `StateFlow`
- UI State as single sealed/data class per screen
- Repository pattern for data layer
- Hilt for dependency injection
- One-way data flow: Event -> ViewModel -> State -> UI

### Patterns

```kotlin
// UI State
data class HomeUiState(
    val isLoading: Boolean = false,
    val items: List<Item> = emptyList(),
    val error: String? = null
)

// ViewModel
class HomeViewModel @Inject constructor(
    private val repo: ItemRepository
) : ViewModel() {
    private val _uiState = MutableStateFlow(HomeUiState())
    val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
}

// Screen (stateful wrapper)
@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    HomeContent(uiState = uiState, onRefresh = viewModel::refresh)
}

// Content (stateless, testable, previewable)
@Composable
fun HomeContent(uiState: HomeUiState, onRefresh: () -> Unit) { ... }
```

### Coroutines

- Use `viewModelScope.launch` for ViewModel operations
- `Dispatchers.IO` for blocking I/O
- `supervisorScope` when child failures shouldn't cancel siblings
- Never use `GlobalScope`
- Never catch `CancellationException` (or rethrow)

### Don'ts

- No `!!` force unwrap  -  use `?.let`, `requireNotNull`, or `checkNotNull`
- No mutable state exposed from ViewModel  -  use `StateFlow` + `asStateFlow()`
- No `Thread.sleep()`  -  use `delay()` in coroutines
- No `android.util.Log` in production  -  use Timber
- No hardcoded strings in Composables  -  use `stringResource()`
- No business logic in Composables  -  put in ViewModel or UseCase

### Testing

- UI: Compose Testing with `createComposeRule()` + `onNodeWithText/Tag`
- ViewModel: `runTest` + `TestDispatcher` + `Turbine` for Flow testing
- Repository: Fake implementations, not mocks
- Naming: `funName_scenario_expectedBehavior()`

### Gradle

- Version catalog: `libs.versions.toml`
- Compose BOM for version alignment
- KSP over KAPT for annotation processing
- Convention plugins for shared build logic
