Kotlin Patterns
Scope Functions
// 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
// 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
// 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
// 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
// Use viewModelScope for ViewModel operations
viewModelScope.launch {
collectDataStateWithInternet(
callFlow = useCase(params),
setState { copy(data = it) } },
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
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
// 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>>