Loading...
Loading...
Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications.
npx skill4agent add chrisbanes/skills kotlin-types-value-class@JvmInline value classUserIdStringStringLongInt| Situation | Prefer |
|---|---|
Single field + domain-meaningful ( | |
| Single field + no domain meaning (just grouping) | Type alias or keep the primitive |
| Multiple fields | Data class |
Needs custom | Data class (value classes delegate to the underlying type) |
| Used as a generic type argument or nullable in hot paths | Data class or primitive (autoboxing cost) |
// GOOD: domain-meaningful single field
@JvmInline value class UserId(val value: String)
@JvmInline value class EmailAddress(val value: String)
@JvmInline value class Percentage(val value: Float)
// BAD: data class wrapping a single field
data class UserId(val value: String) // unnecessary allocation
data class EmailAddress(val value: String) // type safety without the overhead is available
// BAD: value class with no domain meaning
@JvmInline value class Wrapper(val value: String) // just use the String, or a type alias
// BAD: value class needing custom equality
@JvmInline value class CaseInsensitiveString(val value: String)
// value class equals delegates to String equals, which IS case-sensitive
// Use a data class if you need different equality semantics@JvmInline value classStableString@Immutable// Before: data class wrapping a single field
data class UiState(val userId: String) // works, but allocates a wrapper object
// After: value class is stable and zero-allocation at runtime
@JvmInline value class UserId(val value: String)
data class UiState(val userId: UserId)UserId?List<UserId>initlateinitby lazycopy()component1()toString()when@Serializable data class A(val value: String){"value":"..."}@Serializable value class A(val value: String)"..."@SerializableAnypackFloatspackIntsunpack*androidx.compose.ui.utilLong@JvmInline value class Offset(val packedValue: Long)
fun Offset(x: Float, y: Float): Offset = Offset(packFloats(x, y))
val Offset.x: Float get() = unpackFloat1(packedValue)
val Offset.y: Float get() = unpackFloat2(packedValue)androidx.compose.ui.utilpackFloatspackIntsunpackFloat1unpackFloat2unpackInt1unpackInt2| Mistake | Fix |
|---|---|
| Data class wrapping a single domain field | Replace with |
| Value class with no domain meaning (just a wrapper) | Use a type alias or the primitive directly |
| Value class needing custom equality | Use a data class instead |
| Value class as generic type argument in hot path | Accept autoboxing cost or use the primitive |
| Replace with value class — it's Stable by default |
Forgetting | Always pair |
StringLongIntfun transfer(from: String, to: String, amount: Long)@ImmutableequalshashCodetoStringcompose-stability-diagnostics