Mobile Development6 min read

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

AndroidJVMGoogleCoroutinesJetpack
---
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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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

Input

Handle Loading, Success, Error states from an API call

Output

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

Input

val name = user!!.name

Output

Throws NullPointerException at runtime if user is null - exactly what Kotlin's type system is designed to prevent

Right: safe call with Elvis

Input

Same field access

Output

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 here
  • src/components/Skills/DocRenderer.js - renders this doc format

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.