---
name: android-simplify
description: Stage 2 of /android-review. Checks an Android/Kotlin diff for over-engineering and needless complexity (duplicate lifecycle types, stored-not-derived state, nullable state, speculative abstraction, wide public surface, nested control flow, dead weight, off-task edits). Runs only when invoked.
disable-model-invocation: true
argument-hint: "[diff range]"
---

# android-simplify — is there a smaller change that does the same thing?

> From Shubham Singh Bisht's Android helpers — https://shubhamsinghbisht.com/android-roadmap/#helpers

Review only what the diff adds or changes. Report; never edit. Each finding
names the smaller alternative concretely (what to delete, merge or inline).

## 1. Range

`$ARGUMENTS` if given, else the nearest base to the working tree, including untracked files.

The nearest base is main/master/develop or the `release-*` branch this branch is fewest
commits ahead of (a branch already containing HEAD does not count); on main
itself, review only uncommitted work:

```bash
ref=$(for r in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin/main refs/heads/main refs/remotes/origin/master refs/heads/master refs/remotes/origin/develop refs/heads/develop 'refs/remotes/origin/release-*' 'refs/heads/release-*'); do n=$(git rev-list --count "$r..HEAD"); [ "$n" -gt 0 ] && echo "$n $r"; done | sort -n -s | head -1 | cut -d' ' -f2)
base=$( [ -n "$ref" ] && git merge-base HEAD "$ref" || git rev-parse HEAD )
```

## 2. Rules

| ID | Rule |
|---|---|
| SIMP-1 | One lifecycle type per feature. Loading/showing/failed words live in exactly one sealed type; types describing the same lifecycle are merged. |
| SIMP-2 | Derive, don't store. A value computable from existing state gets no field or flow of its own. |
| SIMP-3 | No nullable state. "Maybe" is a named sealed case (`Closed`, `Centered`, `NotOpened`), never `T?` that spreads `?.let` / `?: return` across call sites. |
| SIMP-4 | No speculative abstraction: no interface with one implementation, no base class for one subclass, no manager/helper/provider layer that only forwards, no option nothing sets. |
| SIMP-5 | Tiny public surface: expose the 2–3 calls callers need; precondition guards live in the few callers that need them, not in `require*()` wrappers. |
| SIMP-6 | Flat control flow: early return and `when` over nested `if`; a function whose branches need a comment gets split. |
| SIMP-7 | No dead weight: unused params/imports, commented-out code, leftover debug logs, ownerless TODOs. |
| SIMP-8 | Diff stays on task: unrelated refactors are flagged as a separate task. |

Reference shape for SIMP-1/2/3:

```kotlin
// SIMP-1: every lifecycle word lives in one type.
sealed interface AnswerState {
    data object Preparing : AnswerState
    data class Streaming(val text: String) : AnswerState
    data class Answered(val text: String) : AnswerState
    data class StoppedEarly(val text: String) : AnswerState
    data class Failed(val reason: String) : AnswerState
}

// SIMP-3: "not open" is a named case, not a nullable field, and the
// answer is nested in the case it belongs to.
sealed interface Card {
    data object Closed : Card
    data class Open(val questionId: String, val answer: AnswerState, val isMath: Boolean) : Card
}

// SIMP-2: derived facts are extension properties, not stored fields.
val Card.isOpen: Boolean get() = this is Card.Open
val Card.answerOrNull: AnswerState? get() = (this as? Card.Open)?.answer
```

When something that looks derivable *is* stored (`isMath` above), its KDoc
says why. That is the bar for SIMP-2: store it only with a written reason.

## 3. Output

```
### Stage 2 — android-simplify
| file:line | rule | why (one line) | suggested change |
|---|---|---|---|
```

No findings → `stage 2: nothing found`.
