kotlin-expert

22
4
Source

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.

Install

mkdir -p .claude/skills/kotlin-expert && curl -L -o skill.zip "https://mcp.directory/api/skills/download/1141" && unzip -o skill.zip -d .claude/skills/kotlin-expert && rm skill.zip

Installs to .claude/skills/kotlin-expert

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:

  1. Private mutable, public immutable: _accountState (MutableStateFlow) private, accountState (StateFlow) public
  2. Always has value: Initial value required (LoggedOut)
  3. Single value: Replays ONE most recent value to new subscribers
  4. 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:

ScenarioUse StateFlowUse SharedFlow
UI state✅ Current screen data, login status
One-time events✅ Navigation, snackbars, toasts
Always has value❌ Optional
Replay count1 (latest only)Configurable (0, 1, n)
BackpressureConflates (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:

  1. Closed hierarchy: All subclasses known at compile-time
  2. Exhaustive when: Compiler ensures all cases handled
  3. Shared data: Sealed class can hold common properties
  4. 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:

  1. Multiple inheritance: Subtype can implement other interfaces
  2. Variance: Supports out/in modifiers for generics
  3. No constructor: Can't hold state directly (subtypes can)
  4. Nested hierarchies: Can create sub-sealed hierarchies

Sealed Class vs Sealed Interface

FeatureSealed ClassSealed Interface
Constructor✅ Can hold common state❌ No constructor
Inheritance❌ Single parent only✅ Multiple interfaces
Generics❌ No variance✅ Covariance/contravariance
Use caseState variantsResult 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 data
  • sealed 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:

  1. All properties immutable: Only val, never var
  2. No mutable collections: Use ImmutableList, Array, not MutableList
  3. Deep immutability: Nested objects also immutable
  4. 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:

  1. Structural equality: equals() compares properties, not reference
  2. copy(): Create modified copies without mutating
  3. All properties in constructor: For proper equals()/hashCode()
  4. 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.

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.

13123

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).

304

kotlin-coroutines

vitorpamplona

Advanced Kotlin coroutines patterns for AmethystMultiplatform. Use when working with: (1) Structured concurrency (supervisorScope, coroutineScope), (2) Advanced Flow operators (flatMapLatest, combine, merge, shareIn, stateIn), (3) Channels and callbackFlow, (4) Dispatcher management and context switching, (5) Exception handling (CoroutineExceptionHandler, SupervisorJob), (6) Testing async code (runTest, Turbine), (7) Nostr relay connection pools and subscriptions, (8) Backpressure handling in event streams. Delegates to kotlin-expert for basic StateFlow/SharedFlow patterns. Complements nostr-expert for relay communication.

83

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.

232

gradle-expert

vitorpamplona

Build optimization, dependency resolution, and multi-module KMP troubleshooting for AmethystMultiplatform. Use when working with: (1) Gradle build files (build.gradle.kts, settings.gradle), (2) Version catalog (libs.versions.toml), (3) Build errors and dependency conflicts, (4) Module dependencies and source sets, (5) Desktop packaging (DMG/MSI/DEB), (6) Build performance optimization, (7) Proguard/R8 configuration, (8) Common KMP + Android Gradle issues (Compose conflicts, secp256k1 JNI variants, source set problems).

71

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

1,5711,369

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

1,1161,191

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

1,4181,109

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

1,194747

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

1,154684

pdf-to-markdown

aliceisjustplaying

Convert entire PDF documents to clean, structured Markdown for full context loading. Use this skill when the user wants to extract ALL text from a PDF into context (not grep/search), when discussing or analyzing PDF content in full, when the user mentions "load the whole PDF", "bring the PDF into context", "read the entire PDF", or when partial extraction/grepping would miss important context. This is the preferred method for PDF text extraction over page-by-page or grep approaches.

1,312614

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.