# Kotlin Patterns

> Kotlin coding patterns for this project. Provides guidance on scope functions, collection operations with sequences, sealed class handling, extension functions, coroutine patterns, null safety, and data class usage. Automatically applied when writing Kotlin code in this codebase.

- Skill: `thetruong1099/kotlin-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add thetruong1099/kotlin-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/thetruong1099/kotlin-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: thetruong1099 (https://skillmd.com/u/thetruong1099)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/thetruong1099/kotlin-patterns

---


# Kotlin Patterns

## Scope Functions

```kotlin
// let - null check + transform
val name = user?.let { "${it.firstName} ${it.lastName}" } ?: "Unknown"

// run - execute block on object
val result = item.run { copy(name = name.trim()) }

// apply - configure object
val config = PagingConfig(pageSize = 20).apply { enablePlaceholders = false }

// also - side effects (logging, analytics)
fun getItem(id: String) = repository.getItem(id).also { Log.d(TAG, "Fetched: $id") }

// with - operate on object without chaining
with(viewModel) {
    setState { copy(isLoading = false) }
    showSuccessToast(R.string.saved)
}
```

## Collection Operations

```kotlin
// Prefer sequence for large collections with multiple operations
items.asSequence()
    .filter { it.isActive }
    .sortedByDescending { it.updatedAt }
    .take(10)
    .toList()

val itemsByCategory = items.groupBy { it.category }         // groupBy for categorization
val itemById = items.associateBy { it.id }                  // associate for map creation
val (favorites, others) = items.partition { it.isFavorite } // partition for splitting
```

## Sealed Classes & When Expressions

```kotlin
// Always use exhaustive when for sealed types
when (error) {
    is AppError.NoInternetConnection -> showOfflineUI()
    is AppError.NetworkTimeout       -> showRetryButton()
    is AppError.ServerError          -> showServerError(error.statusCode)
    is AppError.DataParsingError     -> logAndShowGenericError()
    is AppError.Unauthorized         -> navigateToLogin()
    is AppError.Unknown              -> showGenericError()
}
// Compiler errors if new AppError subclass is added but not handled
```

## Extension Functions

```kotlin
// Good: focused, reusable extensions
fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier

fun NavHostController.navigateTo(route: Any, builder: NavOptionsBuilder.() -> Unit = {})

val MaterialTheme.spacing: Spacing @Composable get() = LocalSpacing.current

// Avoid: over-broad extensions
// fun Any.toJson(): String  // Too broad
```

## Coroutine Patterns

```kotlin
// Use viewModelScope for ViewModel operations
viewModelScope.launch {
    collectDataStateWithInternet(
        callFlow = useCase(params),
        onSuccess = { setState { copy(data = it) } },
        onError = { showErrorToast(it) },
    )
}

// Use withContext for dispatcher switching
suspend fun parseHtml(html: String): String = withContext(Dispatchers.Default) {
    Jsoup.parse(html).text()
}

// Prefer Flow operators over manual coroutine management
useCase()
    .catch { emit(DataState.Error(mapError(it))) }
    .collect { /* handle */ }
```

## Null Safety

```kotlin
val displayName = item.author ?: "Unknown Author"                            // Elvis for defaults
item?.chapters?.firstOrNull()?.let { navigateToChapter(it) }                // Safe calls

// Avoid !! - use require/check for assertions
fun processItem(item: Item?) {
    requireNotNull(item) { "Item must not be null" }
    // item is smart-cast to non-null
}
```

## Data Classes

```kotlin
// Use copy() for immutable updates (MVI pattern)
setState { copy(isLoading = true, error = null) }

// Destructuring in lambdas
items.map { (id, name, author) -> "$name by $author" }

// Default values for flexibility
data class SampleViewState(
    val items: Flow<PagingData<SampleModel>>? = null,
    val isRefreshing: Boolean = false,
) : IViewState

// Type aliases for complex generics
typealias ItemPagingFlow = Flow<PagingData<Item>>
typealias DataStateFlow<T> = Flow<DataState<T>>
```

