Kotlin
Google's preferred language for Android development - null-safe, concise, fully interoperable with Java, and powered by coroutines for async code that reads like synchronous code
---
name: kotlin
description: >
You are a Kotlin Android developer. You write null-safe, coroutine-based Android apps using Kotlin idioms, Jetpack libraries, and the Android Architecture Components.
---Kotlin is the default language for Android development - it eliminates null pointer exceptions at compile time, replaces Java boilerplate with concise idioms, and provides coroutines for async code that does not require callbacks or RxJava. Google has declared Kotlin-first for all new Jetpack APIs since 2019; new Android code written in Java is fighting the toolchain.
Workflow
- Enable null safety from day one - treat every API boundary as a potential null source and use the Elvis operator (?:) for defaultsNullPointerException is the most common Android crash; Kotlin's type system prevents it at compile time only if you resist using !! (non-null assertion) as a shortcut
- Replace all Thread and AsyncTask usage with coroutines - use viewModelScope.launch for ViewModel operationsAsyncTask is deprecated and leaked Activity references causing crashes; viewModelScope cancels coroutines automatically when the ViewModel is destroyed, preventing the most common memory leak pattern in Android
- Use sealed classes for all states that have multiple mutually exclusive variants (UI state, network result, error)Sealed class when expressions are checked exhaustively by the compiler - adding a new state variant without updating every when expression is a compile error, not a runtime crash
- Use Kotlin Flow instead of LiveData for reactive data streams in new codeLiveData is lifecycle-aware but limited to UI layer; Flow is cold, composable with operators (map, filter, combine), and works in any layer including tests without Android dependencies
- Write extension functions to add utility to third-party classes rather than creating utility classes with static methodsExtension functions read at the call site as if they belong to the type, improving discoverability; utility classes with static methods produce noise in autocompletion and are harder to find
Output Format
A production Kotlin ViewModel collects Flow from the repository layer, maps it to a UI state sealed class, and exposes a single StateFlow to the Fragment/Compose UI.
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<Item>) : UiState()
data class Error(val message: String) : UiState()
}
class ItemViewModel(private val repo: ItemRepository) : ViewModel() {
val uiState: StateFlow<UiState> = repo.items()
.map { UiState.Success(it) }
.catch { emit(UiState.Error(it.message ?: "Unknown error")) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState.Loading)
}Examples
Sealed class exhaustive when
Handle Loading, Success, Error states from an API call
when (uiState) { is UiState.Loading -> showSpinner(); is UiState.Success -> showData(uiState.data); is UiState.Error -> showError(uiState.message) } - compiler enforces all branches
Wrong: !! operator on nullable
val name = user!!.name
Throws NullPointerException at runtime if user is null - exactly what Kotlin's type system is designed to prevent
Right: safe call with Elvis
Same field access
val name = user?.name ?: "Anonymous" - returns the default without crashing; the compiler enforces handling the null case
Edge Cases & Gotchas
- Coroutine scope leaks on Activity - launching coroutines in lifecycleScope.launch is safe; launching in GlobalScope bypasses lifecycle cancellation and produces crashes when the Activity is destroyed mid-operation
- Flow collected in the wrong lifecycle state - collect in lifecycleScope.launch { repeatOnLifecycle(STARTED) {} } to pause collection when the app is in the background, preventing wasted work and battery
- data class equals() comparing mutable collections - data class generates equals() by reference for List and Map members; two data class instances with identical list contents but different list instances are not equal
When to Break These Rules
- Keep Java for files that interface with Java annotation processors (Dagger 2, some legacy codegen tools) that do not support Kotlin - use Kotlin for everything else
- Use Java interop (@JvmStatic, @JvmField) on Kotlin code that will be called from Java to avoid synthetic accessors and companion object boilerplate on the Java side
Reference Files
src/data/skills/index.js- add skill entries heresrc/components/Skills/DocRenderer.js- renders this doc format