rules/kotlin/coding-style.md
This file extends common/coding-style.md with Kotlin-specific content.
kotlin.code.style=official in gradle.properties)val over var — default to val and only use var when mutation is requireddata class for value types; use immutable collections (List, Map, Set) in public APIsstate.copy(field = newValue)Follow Kotlin conventions:
camelCase for functions and propertiesPascalCase for classes, interfaces, objects, and type aliasesSCREAMING_SNAKE_CASE for constants (const val or @JvmStatic)I: Clickable not IClickable!! — prefer ?., ?:, requireNotNull(), or checkNotNull()?.let {} for scoped null-safe operations// BAD
val name = user!!.name
// GOOD
val name = user?.name ?: "Unknown"
val name = requireNotNull(user) { "User must be set before accessing name" }.name
Use sealed classes/interfaces to model closed state hierarchies:
sealed interface UiState<out T> {
data object Loading : UiState<Nothing>
data class Success<T>(val data: T) : UiState<T>
data class Error(val message: String) : UiState<Nothing>
}
Always use exhaustive when with sealed types — no else branch.
Use extension functions for utility operations, but keep them discoverable:
StringExt.kt, FlowExt.kt)Any or overly generic typesUse the right scope function:
let — null check + transform: user?.let { greet(it) }run — compute a result using receiver: service.run { fetch(config) }apply — configure an object: builder.apply { timeout = 30 }also — side effects: result.also { log(it) }Result<T> or custom sealed typesrunCatching {} for wrapping throwable codeCancellationException — always rethrow ittry-catch for control flow// BAD — using exceptions for control flow
val user = try { repository.getUser(id) } catch (e: NotFoundException) { null }
// GOOD — nullable return
val user: User? = repository.findUser(id)