when subject.// Replace repeated checks against `state` with a subject `when`.
val action = when (state) {
State.SignedOut -> Action.ShowSignIn
is State.SignedIn -> Action.ShowHome(state.user)
}
when or an if chain.| If the code has... | Use... |
|---|---|
| One value being classified | when (subject) |
| Unrelated boolean conditions | Subjectless when or if/else |
| A primary match plus an extra branch-local predicate | Guard condition |
| Invalid input before the main path | Early return, require, or check |
| A closed enum, Boolean, sealed type, or nullable closed type returning a value | Exhaustive when expression |
| Open external input or a real fallback | Explicit else |
return when (event) {
is Event.Message if event.isUnread -> Row.Highlighted(event.message)
is Event.Message -> Row.Normal(event.message)
Event.Empty -> Row.Empty
}
when has a subject.is Type, enum entry, object, value, range, etc.).when expression over a closed domain, handle every case explicitly. Do not add else only to quiet the compiler.is. In a subtype branch, use any caller-visible payload that the mapping
already needs so the smart cast remains explicit; do not replace else with an
invalid bare class name or discard subtype data merely to claim exhaustiveness.val action = when (state) {
SessionState.SignedOut -> Action.ShowSignIn
is SessionState.SignedIn -> Action.ShowHome(state.user)
is SessionState.Expired if state.canRefresh -> Action.Refresh
is SessionState.Expired -> Action.ShowSignIn
}
else when the domain is open: strings from a server, integer status codes, unknown platform values, or a deliberate fallback/logging path.when (status) {
Status.Pending if canRetry -> retry()
Status.Pending -> showPending()
Status.Queued -> showQueued()
}
fun render(user: User?): UiModel {
user ?: return UiModel.SignedOut
return UiModel.SignedIn(
name = user.name,
avatar = user.avatar,
)
}
as, !!, temporary mutable vars, or duplicated casts, keep the original shape or choose a smaller refactor.when// Before
return when (event) {
is Event.Message -> {
if (event.isUnread) Row.Highlighted(event.message) else Row.Normal(event.message)
}
Event.Empty -> Row.Empty
}
// After
return when (event) {
is Event.Message if event.isUnread -> Row.Highlighted(event.message)
is Event.Message -> Row.Normal(event.message)
Event.Empty -> Row.Empty
}
// Before
return when {
result is Result.Success -> Ui.Success(result.value)
result is Result.Failure && result.canRetry -> Ui.Retry(result.error)
result is Result.Failure -> Ui.Error(result.error)
else -> Ui.Loading
}
// After
return when (result) {
is Result.Success -> Ui.Success(result.value)
is Result.Failure if result.canRetry -> Ui.Retry(result.error)
is Result.Failure -> Ui.Error(result.error)
Result.Loading -> Ui.Loading
}
when (value) when null is one branch in a larger classification:return when (val selected = selection) {
null -> SelectionUi.None
is Selection.Single if selected.item.isArchived -> SelectionUi.Archived(selected.item)
is Selection.Single -> SelectionUi.Active(selected.item)
is Selection.Multiple -> SelectionUi.Count(selected.items.size)
}
when expressions remain exhaustive without unnecessary else.as, !!, or duplicated casts.else. RED recommends class names as value branches or
drops the data payload. GREEN uses is for class subtypes, value matches for
objects, and keeps the payload smart-cast where the mapping needs it.else because the domain is open.when already uses each subtype payload without
casts. GREEN reports no control-flow change.when.else for open-world external input.Start with one job and grow from there.