Skip to content
Development
Agent

kotlin-patterns

Replace all `!!` with safe alternatives. `!!` circumvents compile-time null safety.

From plugin
vexjoy-agent
449198 skills198 agents12 commands83 hooks

Claude Code

$ npx -y skills add notque/vexjoy-agent --agent claude-code

Agent definition

kotlin-patterns.md

Kotlin Patterns Reference

Null Safety

Replace all `!!` with safe alternatives. `!!` circumvents compile-time null safety.

Safe Alternatives to `!!`

| Situation | Instead of `!!` | Use | |-----------|----------------|-----| | Value might be null, provide default | `value!!` | `value ?: defaultValue` | | Null means skip/return | `value!!.doSomething()` | `value?.doSomething()` | | Null is a programming error | `value!!` | `requireNotNull(value) { "value must not be null: reason" }` | | Null check in initialization | `lateinit var x: T; x!!` | `checkNotNull(x) { "x not initialized" }` | | Nullable transform chain | `list.find { ... }!!.name` | `list.find { ... }?.name ?: throw NoSuchElementException(...)` |

// BAD -- bypasses null safety
val account = accountRepository.findById(id)!!
val label = config["display_name"]!!

// GOOD -- explicit, descriptive failure
val account = accountRepository.findById(id)
    ?: throw AccountNotFoundException("Account $id not found")
val label = requireNotNull(config["display_name"]) {
    "display_name must be present in config"
}

Java Interop Boundaries

Platform types must be annotated or guarded at the boundary:

// BAD -- platform type passes through silently
fun getHeader(request: HttpServletRequest): String {
    return request.getHeader("X-Request-Id") // String! -- platform type
}

// GOOD -- explicit boundary handling
fun getHeader(request: HttpServletRequest): String? {
    return request.getHeader("X-Request-Id") // explicitly nullable
}

// GOOD -- assert non-null with context
fun getRequiredHeader(request: HttpServletRequest): String {
    return requireNotNull(request.getHeader("X-Request-Id")) {
        "X-Request-Id header is required"
    }
}

**Detection**: `grep -rn '!!' src/` -- any match is a violation requiring immediate review.

---

Coroutines and Flow

Structured Concurrency

Launch within structured scopes (`viewModelScope`, `lifecycleScope`, explicit scopes). Never `GlobalScope` in production.

// BAD -- GlobalScope leaks coroutines
GlobalScope.launch { fetchData() }

// GOOD -- scoped to ViewModel lifecycle
class ProductViewModel(private val repository: ProductRepository) : ViewModel() {
    fun loadProducts() {
        viewModelScope.launch {
            _state.value = repository.getProducts()
        }
    }
}

// GOOD -- scoped in Ktor
fun Application.configureRouting() {
    routing {
        get("/products") {
            val products = coroutineScope {
                async { productService.getAll() }.await()
            }
            call.respond(products)
        }
    }
}

Dispatcher Selection

| Task Type | Dispatcher | Reason | |-----------|-----------|--------| | CPU-intensive computation | `Dispatchers.Default` | Thread pool sized to CPU cores | | Blocking I/O (JDBC, file) | `Dispatchers.IO` | Expandable thread pool for blocking | | Android UI updates | `Dispatchers.Main` | Main thread only | | Ktor request handling | Ktor manages dispatcher | Use `withContext(Dispatchers.IO)` for blocking calls |

// BAD -- blocking JDBC call on Default dispatcher starves CPU threads
suspend fun fetchRecord(id: Long): DbRecord = withContext(Dispatchers.Default) {
    database.find(id) // blocking JDBC
}

// GOOD -- blocking call on IO dispatcher
suspend fun fetchRecord(id: Long): DbRecord = withContext(Dispatchers.IO) {
    database.find(id)
}

Flow Patterns

// StateFlow for UI state with debounced search
class SearchViewModel(private val repo: ProductRepository) : ViewModel() {
    private val _query = MutableStateFlow("")
    val results: StateFlow<List<Product>> = _query
        .debounce(300)
        .distinctUntilChanged()
        .flatMapLatest { query -> repo.search(query) }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

    fun onQueryChanged(query: String) { _query.value = query }
}

Testing Coroutines

Use `runTest` from `kotlinx-coroutines-test`, not `runBlocking`.

// BAD -- runBlocking in tests masks timing issues
@Test
fun `should return products`() = runBlocking {
    val result = viewModel.loadProducts()
    assertEquals(expected, result)
}

// GOOD -- runTest with TestDispatcher provides virtual time control
@Test
fun `should debounce search queries`() = runTest {
    val vm = SearchViewModel(fakeRepo)
    vm.onQueryChanged("ki")
    advanceTimeBy(200) // under debounce threshold
    assertEquals(emptyList(), vm.results.value)
    advanceTimeBy(200) // crosses 300ms threshold
    assertEquals(listOf(product), vm.results.value)
}

---

Sealed Classes, Enums, and Data Classes

Decision Matrix

| Need | Use | Reason | |------|-----|--------| | Fixed set of named constants, no data | `enum class` | Serializable, ordinal, `values()`, simple | | Fixed set of states, each with different data | `sealed class` / `sealed interface` | Exhaustive `when`, each subtype carries its own fields | | Named constant with associated behavior | `enum class` with abstract function | Enum entries can override | | Pure value/record type with structural equality | `data class` | `copy()`, `equals()`, `hashCode()`, destructuring | | Inline wrapper to avoid primitive confusion | `@JvmInline value class` | Zero-overhead at runtime | | Open hierarchy for external extension | `abstract class` or `interface` | Sealed prevents external subclassing |

// Use enum for simple constants
enum class Direction { NORTH, SOUTH, EAST, WEST }

// Use sealed class when variants carry different data
sealed class LoadResult<out T> {
    data class Success<T>(val value: T) : LoadResult<T>()
    data class Failure(val error: Throwable) : LoadResult<Nothing>()
    data object Loading : LoadResult<Nothing>()
}

// Use data class for records
data class UserId(val value: Long)
data class AppUser(val id: UserId, val name: String, val email: String)

// Use value class to avoid primitive confu
Read more
@notque
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.

44964 skills198 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
449
Star history of notque/vexjoy-agent on GitHub, a chart by star-history.com

Other agents on vexjoy-agent.

modules

modules

Agent

**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes…

@notque@notqueInstall Agent
testing

testing

Agent

**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**:…

@notque@notqueInstall Agent
communication-patterns

communication-patterns

Agent

**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what…

@notque@notqueInstall Agent

More agents from other plugins.

a11y-architect
Auto-invoked

a11y-architect

Agent

Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI…

ecc13d293k
@affaan-m@affaan-mInstall Agent
analyze-pass-impact

analyze-pass-impact

Agent

Analyzes how a specific topic affects a group of compiler passes. Used by the /plan-update skill to parallelize research across…

react2d252k
@facebook@facebookInstall Agent
orchestrator

orchestrator

Agent

- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as…

codex2d135k
@openai@openaiInstall Agent
cavecrew-builder

cavecrew-builder

Agent

Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical renames, comment removal, format-preserving tweaks. Hard…

caveman3d119k
@juliusbrussee@juliusbrusseeInstall Agent

Questions about kotlin-patterns.

Which coding agents can use kotlin-patterns?
kotlin-patterns installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.