kotlin-expert
Provides patterns for advanced Kotlin state management, sealed classes, and Compose performance.
Install
mkdir -p .claude/skills/kotlin-expert && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/1141" && unzip -o skill.zip -d .claude/skills/kotlin-expert && rm skill.zipInstalls to .claude/skills/kotlin-expert
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.Key capabilities
- →Constructs type-safe DSLs with lambda receivers
- →Enforces StateFlow/SharedFlow patterns
- →Optimizes Compose state with @Immutable
- →Implements sealed interface hierarchies
How it works
Applies high-level Kotlin architectural idioms such as inline functions and reified generics to reduce boilerplace and overhead.
Inputs & outputs
When to use kotlin-expert
- →Implement StateFlow for state management
- →Refactor code to use sealed hierarchies
- →Optimize Compose components for performance
About this skill
Kotlin Expert
Advanced Kotlin patterns for AmethystMultiplatform. Covers Flow state management, sealed hierarchies, immutability, DSL builders, and inline functions with real codebase examples.
Mental Model
Kotlin in Amethyst:
State Management (Hot Flows)
├── StateFlow<T> # Single value, always has value, replays to new subscribers
├── SharedFlow<T> # Event stream, configurable replay, multiple subscribers
└── MutableStateFlow<T> # Private mutable, public via .asStateFlow()
Type Safety (Sealed Hierarchies)
├── sealed class # State variants with data (AccountState.LoggedIn/LoggedOut)
└── sealed interface # Generic result types (SignerResult<T>)
Compose Performance (@Immutable)
├── @Immutable # 173+ event classes - prevents recomposition
└── data class # Structural equality, copy(), immutable by convention
DSL Patterns
├── Builder classes # Fluent APIs (TagArrayBuilder)
├── Lambda receivers # inline fun tagArray { ... }
└── Method chaining # return this
Performance
├── inline fun # Eliminate lambda overhead
├── reified type params # Runtime type info (OptimizedJsonMapper)
└── value class # Zero-cost wrappers (NOT USED yet in Amethyst)
Delegation:
- kotlin-coroutines agent: Deep async (structured concurrency, channels, operators)
- kotlin-multiplatform skill: expect/actual, source sets
- This skill: Amethyst Kotlin idioms, state patterns, type safety
1. Flow State Management
StateFlow: State that Changes
Mental model: StateFlow is a "hot" observable state holder. Always has a value, new collectors immediately get current state.
Amethyst pattern:
// AccountManager.kt:48-50
class AccountManager {
private val _accountState = MutableStateFlow<AccountState>(AccountState.LoggedOut)
val accountState: StateFlow<AccountState> = _accountState.asStateFlow()
fun login(key: String) {
_accountState.value = AccountState.LoggedIn(...)
}
}
Key principles:
- Private mutable, public immutable:
_accountState(MutableStateFlow) private,accountState(StateFlow) public - Always has value: Initial value required (
LoggedOut) - Single value: Replays ONE most recent value to new subscribers
- Hot: Stays in memory, all collectors share same instance
See: AccountManager.kt:48-50, RelayConnectionManager.kt:49-52
SharedFlow: Event Streams
Mental model: SharedFlow is a "hot" broadcast stream for events. Configurable replay buffer, doesn't require initial value.
Amethyst pattern:
// RelayConnectionManager.kt:52-53
val connectedRelays: StateFlow<Set<NormalizedRelayUrl>> = client.connectedRelaysFlow()
val availableRelays: StateFlow<Set<NormalizedRelayUrl>> = client.availableRelaysFlow()
When to use StateFlow vs SharedFlow:
| Scenario | Use StateFlow | Use SharedFlow |
|---|---|---|
| UI state | ✅ Current screen data, login status | ❌ |
| One-time events | ❌ | ✅ Navigation, snackbars, toasts |
| Always has value | ✅ | ❌ Optional |
| Replay count | 1 (latest only) | Configurable (0, 1, n) |
| Backpressure | Conflates (drops old) | Configurable buffer |
Best practice:
// State: Use StateFlow
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// Events: Use SharedFlow
private val _navigationEvents = MutableSharedFlow<NavEvent>(replay = 0)
val navigationEvents: SharedFlow<NavEvent> = _navigationEvents.asSharedFlow()
Flow Anti-Patterns
❌ Exposing mutable state:
val accountState: MutableStateFlow<AccountState> // BAD: Can be mutated externally
✅ Expose immutable:
val accountState: StateFlow<AccountState> = _accountState.asStateFlow() // GOOD
❌ SharedFlow for state:
val loginState = MutableSharedFlow<LoginState>() // BAD: State might get lost
✅ StateFlow for state:
val loginState = MutableStateFlow(LoginState.LoggedOut) // GOOD: Always has value
See: references/flow-patterns.md for comprehensive examples.
2. Sealed Hierarchies
Sealed Classes: State Variants
Mental model: Sealed classes represent a closed set of variants that share common data/behavior.
Amethyst pattern:
// AccountManager.kt:36-46
sealed class AccountState {
data object LoggedOut : AccountState()
data class LoggedIn(
val signer: NostrSigner,
val pubKeyHex: String,
val npub: String,
val nsec: String?,
val isReadOnly: Boolean
) : AccountState()
}
// Usage
when (state) {
is AccountState.LoggedOut -> showLogin()
is AccountState.LoggedIn -> showFeed(state.pubKeyHex)
} // Exhaustive - compiler enforces all cases
Key principles:
- Closed hierarchy: All subclasses known at compile-time
- Exhaustive when: Compiler ensures all cases handled
- Shared data: Sealed class can hold common properties
- Single inheritance: Subclass can't extend another class
When to use:
- Modeling UI states (Loading, Success, Error)
- Login states (LoggedOut, LoggedIn)
- Result types with different data per variant
Sealed Interfaces: Generic Result Types
Mental model: Sealed interfaces for contracts with multiple implementations that need generics or multiple inheritance.
Amethyst pattern:
// SignerResult.kt:25-46
sealed interface SignerResult<T : IResult> {
sealed interface RequestAddressed<T : IResult> : SignerResult<T> {
class Successful<T : IResult>(val result: T) : RequestAddressed<T>
class Rejected<T : IResult> : RequestAddressed<T>
class TimedOut<T : IResult> : RequestAddressed<T>
class ReceivedButCouldNotPerform<T : IResult>(
val message: String?
) : RequestAddressed<T>
}
}
// Usage with generics
fun handleResult(result: SignerResult<SignResult>) {
when (result) {
is SignerResult.RequestAddressed.Successful -> processEvent(result.result.event)
is SignerResult.RequestAddressed.Rejected -> showRejected()
is SignerResult.RequestAddressed.TimedOut -> showTimeout()
}
}
Key principles:
- Multiple inheritance: Subtype can implement other interfaces
- Variance: Supports
out/inmodifiers for generics - No constructor: Can't hold state directly (subtypes can)
- Nested hierarchies: Can create sub-sealed hierarchies
Sealed Class vs Sealed Interface
| Feature | Sealed Class | Sealed Interface |
|---|---|---|
| Constructor | ✅ Can hold common state | ❌ No constructor |
| Inheritance | ❌ Single parent only | ✅ Multiple interfaces |
| Generics | ❌ No variance | ✅ Covariance/contravariance |
| Use case | State variants | Result types, contracts |
Decision tree:
Need to hold common data in base?
YES → sealed class
NO → sealed interface
Need generics with variance (out/in)?
YES → sealed interface
NO → Either works
Subtypes need multiple inheritance?
YES → sealed interface
NO → Either works
Amethyst examples:
sealed class AccountState- state variants with different datasealed interface SignerResult<T>- generic result types with variance
See: references/sealed-class-catalog.md for all sealed types in quartz.
3. Immutability & Compose Performance
@Immutable Annotation
Mental model: @Immutable tells Compose "this value never changes after construction." Compose can skip recomposition if @Immutable object reference doesn't change.
Amethyst pattern:
// TextNoteEvent.kt:51-63
@Immutable
class TextNoteEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey
) : BaseThreadedEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
// All properties immutable (val), no mutable state
}
Key principles:
- All properties immutable: Only
val, nevervar - No mutable collections: Use
ImmutableList,Array, notMutableList - Deep immutability: Nested objects also immutable
- Compose optimization: Skips recomposition if reference equals
Why it matters:
// Without @Immutable
@Composable
fun NoteCard(note: TextNoteEvent) { // Recomposes every time parent recomposes
Text(note.content)
}
// With @Immutable
@Composable
fun NoteCard(note: TextNoteEvent) { // Only recomposes if note reference changes
Text(note.content)
}
173+ @Immutable classes in quartz - all events immutable for Compose performance.
Data Classes & Immutability
Pattern:
@Immutable
data class RelayStatus(
val url: NormalizedRelayUrl,
val connected: Boolean,
val error: String? = null
) {
// Implicit: equals(), hashCode(), copy(), toString()
}
// Usage
val oldStatus = RelayStatus(url, connected = false)
val newStatus = oldStatus.copy(connected = true) // Immutable update
Key principles:
- Structural equality:
equals()compares properties, not reference - copy(): Create modified copies without mutating
- All properties in constructor: For proper
equals()/hashCode() - Prefer val: Make properties immutable
kotlinx.collections.immutable
Pattern:
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
// Instead of List (which could be mutable internally)
val relays: ImmutableList<String> = persistentListOf("wss://relay1.com", "wss://relay2.com")
// Add returns new instance
val updated = relays.add("wss://relay3.com") // relays unchanged, updated has 3 items
Content truncated.
When not to use it
- →For simple script-based Kotlin
- →In projects strictly avoiding reactive state flows
Prerequisites
Limitations
- →Requires Kotlin compiler proficiency
- →Focuses on complex architectural patterns
How it compares
It provides architecture-specific patterns tailored to the Amethyst codebase instead of generic language syntax help.
Compared to similar skills
kotlin-expert side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| kotlin-expert (this skill) | 5 | 2mo | No flags | Advanced |
| kmp-cmp-app-builder | 0 | 4mo | Review | Advanced |
| android-kotlin-development | 268 | 5mo | Review | Advanced |
| kotlin-multiplatform | 32 | 3mo | Review | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by vitorpamplona
View all by vitorpamplona →You might also like
kmp-cmp-app-builder
firdaus1453
>
android-kotlin-development
aj-geddes
Develop native Android apps with Kotlin. Covers MVVM with Jetpack, Compose for modern UI, Retrofit for API calls, Room for local storage, and navigation architecture.
kotlin-multiplatform
vitorpamplona
Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.
compose-expert
vitorpamplona
Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).
android-kotlin
alinaqi
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
nostr-expert
vitorpamplona
Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details.