Skip to content
Back to skills

Kotlin Pro

ASecurity

Idiomatic Kotlin: null safety, coroutines, data classes, sealed hierarchies, and Android/JVM patterns. Use when writing, reviewing, or structuring Kotlin code.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
ai-agentsgojavakotlintestingapi

Works with

  • api

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add aicodedecode/awesome-muse-skills --skill kotlin-pro --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Kotlin Pro?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Kotlin Pro
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aicodedecode-kotlin-pro/badge)](https://www.skillsdirectory.com/skills/aicodedecode-kotlin-pro)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: kotlin-pro
description: Idiomatic Kotlin: null safety, coroutines, data classes, sealed hierarchies, and Android/JVM patterns. Use when writing, reviewing, or structuring Kotlin code.
category: development
---

# Kotlin Pro

## Overview

Kotlin's pitch — **a pragmatic, null-safe, concise language for the JVM and Android** — is
delivered through its type system and coroutines. Professional Kotlin means using null safety as
designed (not fighting it with `!!`), modeling with data classes and sealed hierarchies, writing
suspend functions instead of callback chains, and keeping the functional sugar readable rather
than clever.

The through-line: concise but explicit — Kotlin brevity should clarify, never obscure.

## When to use

- Writing or reviewing Kotlin for idiom and safety (JVM, Android, or Multiplatform).
- Designing with null safety, data classes, and sealed types.
- Using coroutines and Flow for async work.
- Structuring Android apps (ViewModel, repositories, DI).
- Setting up testing, linting (detekt), and CI for Kotlin.

## Core concepts

- **Null safety is the point.** Nullable (`String?`) vs non-null (`String`) is a compile-time
  contract. Use safe calls (`?.`), Elvis (`?:`) with meaningful defaults or early returns, and
  `let`/`also` scoping deliberately. `!!` is a bug with a timer — each one needs a justification.
- **Data classes for data.** `data class` gives equals/hashCode/copy/destructuring for free.
  Immutable (`val`) by default; `copy()` for variations. Perfect for DTOs, UI state, and domain
  values — stop writing Java-style POJOs.
- **Sealed hierarchies for states.** `sealed interface UiState` with `data object Loading`,
  `data class Content(...)`, `data class Error(...)` — then `when` is exhaustive and the compiler
  enforces handling every case. This pattern alone eliminates a huge class of UI bugs.
- **Coroutines, structured.** `suspend` functions for async work; `viewModelScope`/`lifecycleScope`
  on Android so cancellation follows the lifecycle; `Flow` for streams of values, `StateFlow` for
  observable state. Never `GlobalScope.launch` — unstructured concurrency leaks.
- **Extension functions with restraint.** Great for domain-specific readability
  (`String.toSlug()`), terrible as dumping grounds. Keep them discoverable (file per domain) and
  avoid shadowing stdlib names.
- **Idiomatic stdlib.** `let`/`run`/`apply`/`also`/`with` — learn which returns what (`apply`/`also`
  return receiver; `let`/`run` return lambda result). Scope functions clarify *intent* when used
  well and obfuscate when chained thoughtlessly.

## Practical workflow

1. **Model with types.** Data classes immutable, sealed interfaces for states/results, enums for
   fixed sets. Nullability explicit at every boundary.
2. **Structure Android apps in layers.** UI (Compose/Views, dumb) → ViewModel (state holder,
   survives config change) → Repository (single source of truth, mediates sources) → Data sources
   (network/DB). DI (Hilt/Koin) wires it; manual DI is fine for small apps.
3. **Write coroutines structurally.** `suspend` all the way down for I/O; dispatchers explicit
   (`Dispatchers.IO` for blocking I/O); `Flow` with proper sharing (`stateIn`/`shareIn`) for
   hot streams; handle cancellation cooperatively (check `isActive` in long loops).
4. **Test the logic.** JUnit 5 + Turbine for Flows, MockK for mocks, coroutines test dispatcher
   for deterministic async tests. Test ViewModels and repositories; keep Android-framework
   dependencies behind interfaces.
5. **Lint and format.** ktlint or Spotless for formatting (non-negotiable in CI), detekt for
   complexity/smell rules tuned to the team — not every default rule.
6. **Mind the platform.** On Android: don't do I/O on main, respect lifecycle, test on low-end
   devices; on JVM: coroutines over raw threads, structured logging, graceful shutdown.

Idiomatic snippets:

```kotlin
// Sealed UI state + exhaustive when
sealed interface UiState<out T> {
    data object Loading : UiState<Nothing>
    data class Content<T>(val data: T) : UiState<T>
    data class Error(val message: String) : UiState<Nothing>
}

fun render(state: UiState<List<Order>>) = when (state) {
    is UiState.Loading -> showSpinner()
    is UiState.Content -> showOrders(state.data)
    is UiState.Error -> showError(state.message)
}

// Repository with Flow, single source of truth
class OrderRepository(private val api: OrderApi, private val dao: OrderDao) {
    fun orders(): Flow<List<Order>> = dao.observe()
        .onStart { refresh() }
    private suspend fun refresh() { /* fetch → cache → dao.insert */ }
}
```

## Common pitfalls

- **`!!` everywhere.** Defeats the entire language. If you're sure it's non-null, prove it to the
  compiler (check + smart cast) instead of asserting blindly.
- **`GlobalScope.launch`.** Fire-and-forget coroutines that outlive their purpose, leak resources,
  and crash after the screen is gone. Always scope to a lifecycle.
- **Blocking in coroutines.** Calling blocking I/O on `Dispatchers.Default` starves the shared
  pool. Use `Dispatchers.IO` or `withContext` appropriately.
- **Mutable shared state across coroutines.** `var` mutated from multiple coroutines without
  synchronization — the same race conditions Java had, now with nicer syntax. Use actors,
  `Mutex`, or confined state.
- **Over-chained scope functions.** `foo?.let { it.bar().also { ... }.run { ... } }` — write it
  as statements when the chain stops clarifying. Brevity ≠ readability.
- **Data class misuse.** Mutable `var` fields in data classes break equals/hashCode contracts;
  data classes with no real data (behavior-only) should be regular classes or objects.
- **Leaking Android framework into domain.** `Context` in repositories, Android classes in pure
  logic — untestable and unportable. Keep the domain pure; adapt at the edges.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…