com um clique
kotlin-types-value-class
// Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications.
// Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications.
Use when writing or reviewing Jetpack Compose layout APIs, modifier parameters, modifier chain construction, hardcoded root layout decisions, or layout wrappers around a single conditional.
Use when investigating Jetpack Compose recomposition performance, skippable/restartable composables, composables.txt or compiler reports, Layout Inspector recomposition counts, back-writing snapshot state across phases, or frame-rate State reads in composition vs layout/draw, and it is not yet clear whether the cause is parameter stability, deferred reads, or cross-phase back-writing.
Use when writing or reviewing Jetpack Compose code with LaunchedEffect, DisposableEffect, SideEffect, rememberCoroutineScope, rememberUpdatedState, snapshotFlow, snackbar, navigation, focus requests, analytics, or event Flow collection.
Use when writing or reviewing Jetpack Compose parameter stability, compiler reports, skippability, unstable UI state classes, collection parameters, or Kotlin 2.0+ strong skipping behavior.
Use when writing or reviewing Jetpack Compose code with bare local var in a @Composable, remember { mutableStateOf(...) }, mutableStateListOf/mutableStateMapOf, or @ReadOnlyComposable.
Use when Jetpack Compose code reads scroll, animation, gesture, or other frame-rate State in composition, passes changing values across composable boundaries, uses value-form layout/draw modifiers, or back-writes observable state from a later phase into one that's already run.
| name | kotlin-types-value-class |
| description | Use when writing or reviewing Kotlin type declarations to choose @JvmInline value class over data class where appropriate, including Compose stability implications. |
Prefer @JvmInline value class for single-field types that carry domain meaning. Data classes are for aggregating multiple fields. A value class gives you type safety (you can't mix up UserId and String) without the allocation overhead of a data class.
String, Long, Int, etc.) used where a domain type would prevent misuse| Situation | Prefer |
|---|---|
Single field + domain-meaningful (UserId, EmailAddress, Percentage) | @JvmInline value class |
| Single field + no domain meaning (just grouping) | Type alias or keep the primitive |
| Multiple fields | Data class |
Needs custom equals/hashCode beyond the wrapped value | 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 class is treated as Stable by the Compose compiler when its underlying type is stable (primitives, String, and other stable types). This means:
@Immutable annotations at Compose boundaries when wrapping primitives or strings// 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?), generic type arguments (List<UserId>), or vararg parameters. In hot paths these allocations matter; in most code they don't.init blocks, lateinit, or delegated properties like by lazy. The class body is extremely constrained — only the single constructor parameter exists.copy(), no component1() for destructuring. If you need these, use a data class. You can override toString() in a value class, but the default is ClassName(fieldName=value) — it does not delegate to the underlying type's toString(). Override it yourself if you need a different representation.when branches carefully.@Serializable data class A(val value: String) serializes as {"value":"..."}, but a @Serializable value class A(val value: String) serializes as the underlying value ("..."). Replacing a single-field data class with a value class is a breaking change for your API/JSON contract.@Serializable works, but Jackson may need configuration).Any or used in generic contexts, value classes box into a synthetic wrapper class. Java reflection sees mangled method signatures, and frameworks that rely on raw runtime types (some ORMs, DI containers, or serializers) may see the underlying type rather than the value class.A value class can only declare one field, but Compose provides packFloats, packInts, and matching unpack* functions in androidx.compose.ui.util to store multiple primitives in a single Long. This lets you represent composite values (e.g., a 2D point, size, or padding) as a zero-allocation value class instead of a multi-field data class.
@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.util — packFloats, packInts, unpackFloat1, unpackFloat2, unpackInt1, unpackInt2.| Mistake | Fix |
|---|---|
| Data class wrapping a single domain field | Replace with @JvmInline value class |
| 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 |
@Immutable annotation on a type that could be a value class | Replace with value class — it's Stable by default |
Forgetting @JvmInline annotation | Always pair value class with @JvmInline for single-field classes |
String, Long, or Int used where different values should not be interchangeable (e.g., fun transfer(from: String, to: String, amount: Long))@Immutable annotation on a single-field wrapperequals/hashCode → data classcompose-stability-diagnostics — diagnose unstable Compose parameters; value classes are one fix