Skip to content

Commit a7eb472

Browse files
authored
Prepare for release 0.7.3 (#148)
1 parent 2753a12 commit a7eb472

File tree

9 files changed

+119
-37
lines changed

9 files changed

+119
-37
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [0.7.2] - 2026-04-02
8+
## [0.7.3] - 2026-04-11
99

1010
### Added
1111
- **Stability configuration file support for `stabilityDump`** (Issue #130, PR #105)
@@ -17,6 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Reduces baseline file size in large projects and focuses on stability issues
1818
- **New composable diff now includes parameter-level stability details** (PR #105)
1919
- `stabilityCheck` output for new composables shows each parameter's stability status
20+
- **Internal state change tracking for `@TraceRecomposition`** (Issue #89)
21+
- New `traceStates` annotation parameter: `@TraceRecomposition(traceStates = true)`
22+
- Tracks `mutableStateOf`, `mutableIntStateOf`, `derivedStateOf` and other Compose state changes
23+
- Compiler plugin detects delegated state variables via `IrLocalDelegatedProperty` IR analysis
24+
- Logs state changes with `[state]` prefix, parameter changes with `[param]` prefix
25+
- Only changed states are logged to reduce noise
2026

2127
### Fixed
2228
- **`ignoredPackages` now consistently respected during `stabilityCheck`** (Issue #129)

README.md

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ This is incredibly useful for:
212212
First, add the plugin to the `[plugins]` section of your `libs.versions.toml` file:
213213

214214
```toml
215-
stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version = "0.7.2" }
215+
stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version = "0.7.3" }
216216
```
217217

218218
Then, apply it to your root `build.gradle.kts` with `apply false`:
@@ -234,7 +234,7 @@ It’s **strongly recommended to use the exact same Kotlin version** as this lib
234234

235235
| Stability Analyzer | Kotlin |
236236
|--------------------|-------------|
237-
| 0.7.2+ | 2.3.20 |
237+
| 0.7.3+ | 2.3.20 |
238238
| 0.6.5~0.7.0 | 2.3.0 |
239239
| 0.4.0~0.6.4 | 2.2.21 |
240240

@@ -257,9 +257,9 @@ That's it. When this composable recomposes, you'll see logs like:
257257

258258
```
259259
D/Recomposition: [Recomposition #1] UserProfile
260-
D/Recomposition: └─ user: User stable (User@abc123)
260+
D/Recomposition: └─ [param] user: User stable (User@abc123)
261261
D/Recomposition: [Recomposition #2] UserProfile
262-
D/Recomposition: └─ user: User changed (User@abc123 → User@def456)
262+
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
263263
```
264264

265265
### Annotation Parameters
@@ -317,7 +317,7 @@ Now logs include the tag:
317317

318318
```
319319
D/Recomposition: [Recomposition #1] UserProfile (tag: user-profile)
320-
D/Recomposition: └─ user: User stable (User@abc123)
320+
D/Recomposition: └─ [param] user: User stable (User@abc123)
321321
```
322322

323323
This is also very useful if you want to set a custom logger for `ComposeStabilityAnalyzer`, to distinguish which composable function should be examined like the example below:
@@ -415,7 +415,7 @@ Let's understand what each log tells you:
415415

416416
```
417417
D/Recomposition: [Recomposition #1] UserProfile
418-
D/Recomposition: └─ user: User stable (User@abc123)
418+
D/Recomposition: └─ [param] user: User stable (User@abc123)
419419
```
420420

421421
**What this means:**
@@ -431,7 +431,7 @@ This log confirms the composable is working correctly. The parameter is stable a
431431

432432
```
433433
D/Recomposition: [Recomposition #2] UserProfile
434-
D/Recomposition: └─ user: User changed (User@abc123 → User@def456)
434+
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
435435
```
436436

437437
**What this means:**
@@ -445,17 +445,17 @@ This is normal behavior. The parameter changed, so the composable recomposed to
445445

446446
```
447447
D/Recomposition: [Recomposition #1] UserCard (tag: user-card)
448-
D/Recomposition: ├─ user: MutableUser unstable (MutableUser@xyz789)
448+
D/Recomposition: ├─ [param] user: MutableUser unstable (MutableUser@xyz789)
449449
D/Recomposition: └─ Unstable parameters: [user]
450450
```
451451

452452
#### Multiple Parameters (Mixed Stability)
453453

454454
```
455455
D/Recomposition: [Recomposition #5] ProductList (tag: products)
456-
D/Recomposition: ├─ title: String stable (Products)
457-
D/Recomposition: ├─ count: Int changed (4 → 5)
458-
D/Recomposition: ├─ items: List<Product> unstable (List@abc)
456+
D/Recomposition: ├─ [param] title: String stable (Products)
457+
D/Recomposition: ├─ [param] count: Int changed (4 → 5)
458+
D/Recomposition: ├─ [param] items: List<Product> unstable (List@abc)
459459
D/Recomposition: └─ Unstable parameters: [items]
460460
```
461461

@@ -492,13 +492,13 @@ fun ProductCard(
492492

493493
```
494494
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
495-
D/Recomposition: ├─ product: Product unstable (Product@abc)
496-
D/Recomposition: ├─ onClick: () -> Unit stable (Function@xyz)
495+
D/Recomposition: ├─ [param] product: Product unstable (Product@abc)
496+
D/Recomposition: ├─ [param] onClick: () -> Unit stable (Function@xyz)
497497
D/Recomposition: └─ Unstable parameters: [product]
498498
499499
D/Recomposition: [Recomposition #4] ProductCard (tag: product-card)
500-
D/Recomposition: ├─ product: Product unstable (Product@abc)
501-
D/Recomposition: ├─ onClick: () -> Unit stable (Function@xyz)
500+
D/Recomposition: ├─ [param] product: Product unstable (Product@abc)
501+
D/Recomposition: ├─ [param] onClick: () -> Unit stable (Function@xyz)
502502
D/Recomposition: └─ Unstable parameters: [product]
503503
504504
... (logs continue every scroll)
@@ -537,12 +537,47 @@ Run the app again and check Logcat:
537537

538538
```
539539
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
540-
D/Recomposition: ├─ product: Product stable (Product@abc)
541-
D/Recomposition: └─ onClick: () -> Unit stable (Function@xyz)
540+
D/Recomposition: ├─ [param] product: Product stable (Product@abc)
541+
D/Recomposition: └─ [param] onClick: () -> Unit stable (Function@xyz)
542542
543543
(No more excessive recompositions!)
544544
```
545545

546+
### Internal State Tracking
547+
548+
By default, `@TraceRecomposition` only tracks **parameter changes**. When a composable recomposes due to internal state changes (`mutableStateOf`, `derivedStateOf`, etc.), the standard logs show nothing because the parameters haven't changed.
549+
550+
Setting `traceStates = true` enables **internal state tracking**:
551+
552+
```kotlin
553+
@TraceRecomposition(traceStates = true)
554+
@Composable
555+
fun CounterScreen(title: String) {
556+
var counter by remember { mutableIntStateOf(0) }
557+
val doubled by remember { derivedStateOf { counter * 2 } }
558+
559+
Column {
560+
Text("$title: $counter (doubled: $doubled)")
561+
Button(onClick = { counter++ }) {
562+
Text("Increment")
563+
}
564+
}
565+
}
566+
```
567+
568+
After clicking the button:
569+
570+
```
571+
D/Recomposition: [Recomposition #2] CounterScreen
572+
D/Recomposition: ├─ [param] title: String stable (Counter)
573+
D/Recomposition: ├─ [state] counter: Int changed (0 → 1)
574+
D/Recomposition: └─ State changes: [counter]
575+
```
576+
577+
The `[param]` prefix identifies parameter entries, while `[state]` identifies internal state changes. Only states that actually changed are logged. This helps answer the question: "Why is this composable recomposing when parameters haven't changed?"
578+
579+
> **Note**: State tracking detects delegated state properties (`var x by remember { mutableStateOf(...) }`). Non-delegated patterns (`val state = mutableStateOf(...)`) are not tracked in the current version.
580+
546581
### Best Practices
547582

548583
**1. Don't track everything**: Be selective about which composables you track. Focus on:

compose-stability-analyzer-idea/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
All notable changes to the IntelliJ IDEA plugin will be documented in this file.
44

5-
## [0.7.2] - 2026-04-02
5+
## [0.7.3] - 2026-04-11
66

77
### Fixed
88
- **ADB not found on Windows** (Issue #139)

compose-stability-analyzer-idea/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ kotlin {
2626
}
2727

2828
group = "com.github.skydoves"
29-
version = "0.7.2"
29+
version = "0.7.3"
3030

3131
repositories {
3232
mavenLocal()

docs/gradle-plugin/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ The Compose Stability Analyzer compiler plugin is tightly coupled to the Kotlin
4545

4646
| Stability Analyzer | Kotlin |
4747
|--------------------|--------|
48-
| 0.7.2+ | 2.3.20 |
48+
| 0.7.3+ | 2.3.20 |
4949
| 0.6.5 ~ 0.7.0 | 2.3.0 |
5050
| 0.4.0 ~ 0.6.4 | 2.2.21 |
5151

docs/gradle-plugin/trace-recomposition.md

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ When this composable recomposes, detailed logs appear in Logcat showing the reco
2121

2222
```
2323
D/Recomposition: [Recomposition #1] UserProfile
24-
D/Recomposition: └─ user: User stable (User@abc123)
24+
D/Recomposition: └─ [param] user: User stable (User@abc123)
2525
D/Recomposition: [Recomposition #2] UserProfile
26-
D/Recomposition: └─ user: User changed (User@abc123 → User@def456)
26+
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
2727
```
2828

2929
The first log entry shows the initial composition: the `user` parameter is stable and the log includes its identity hash. The second entry shows a recomposition triggered by the `user` parameter changing to a different instance, with both the old and new identity hashes displayed.
@@ -46,7 +46,7 @@ Logs now include the tag, making it easy to filter in Logcat:
4646

4747
```
4848
D/Recomposition: [Recomposition #1] UserProfile (tag: user-profile)
49-
D/Recomposition: └─ user: User stable (User@abc123)
49+
D/Recomposition: └─ [param] user: User stable (User@abc123)
5050
```
5151

5252
Tags are also valuable when using a [custom logger](custom-logger.md), since you can route events differently based on their tag, for example sending only critical flow recompositions (checkout, authentication) to your analytics platform while logging everything else to Logcat during development.
@@ -69,6 +69,47 @@ fun FrequentlyRecomposingScreen() {
6969

7070
A threshold of `3` works well for most composables. For composables in scrolling lists or frequently updating screens where some recomposition is expected, a higher threshold (e.g., `10` or `20`) helps focus on truly excessive recomposition.
7171

72+
### The `traceStates` Parameter
73+
74+
By default, `@TraceRecomposition` only tracks **parameter changes**. When a composable recomposes due to internal state changes (`mutableStateOf`, `derivedStateOf`, etc.), the standard logs show nothing because the parameters haven't changed.
75+
76+
Setting `traceStates = true` enables **internal state tracking**. The compiler plugin detects state variable declarations in your composable and injects tracking code that logs which states changed between recompositions.
77+
78+
```kotlin
79+
@TraceRecomposition(traceStates = true)
80+
@Composable
81+
fun CounterScreen(title: String) {
82+
var counter by remember { mutableIntStateOf(0) }
83+
val doubled by remember { derivedStateOf { counter * 2 } }
84+
85+
Column {
86+
Text("$title: $counter (doubled: $doubled)")
87+
Button(onClick = { counter++ }) {
88+
Text("Increment")
89+
}
90+
}
91+
}
92+
```
93+
94+
After clicking the button, the log now shows both parameter and state information:
95+
96+
```
97+
D/Recomposition: [Recomposition #2] CounterScreen
98+
D/Recomposition: ├─ [param] title: String stable (Counter)
99+
D/Recomposition: ├─ [state] counter: Int changed (0 → 1)
100+
D/Recomposition: └─ State changes: [counter]
101+
```
102+
103+
The `[param]` prefix identifies parameter tracking entries, while `[state]` identifies internal state changes. Only states that actually changed are logged, reducing noise. The `State changes` summary at the end lists all changed state variable names for quick reference.
104+
105+
!!! note "Supported state patterns"
106+
107+
State tracking detects delegated state properties: `var x by remember { mutableStateOf(...) }`, `mutableIntStateOf`, `mutableLongStateOf`, `mutableFloatStateOf`, `mutableDoubleStateOf`, and `derivedStateOf`. Non-delegated patterns (`val state = mutableStateOf(...)`) are not tracked in the current version. Use the `by` delegation syntax for full tracking support.
108+
109+
!!! tip "When to use traceStates"
110+
111+
Use `traceStates = true` when a composable is recomposing but the parameter logs show no changes. This typically means an internal state or `CompositionLocal` is causing the recomposition, and state tracking will reveal which one.
112+
72113
## Reading the Logs
73114

74115
Understanding the log output is key to diagnosing recomposition issues. Each log entry contains several pieces of information that, together, tell you exactly what happened and why.
@@ -77,7 +118,7 @@ Understanding the log output is key to diagnosing recomposition issues. Each log
77118

78119
```
79120
D/Recomposition: [Recomposition #1] UserProfile
80-
D/Recomposition: └─ user: User stable (User@abc123)
121+
D/Recomposition: └─ [param] user: User stable (User@abc123)
81122
```
82123

83124
The `[Recomposition #1]` counter tells you this is the first time this composable instance is recomposing. `user: User` identifies the parameter by name and type. The `stable` label means the Compose compiler considers this parameter stable, so it won't cause unnecessary recompositions. The identity hash `(User@abc123)` lets you track whether the same instance is being passed across recompositions.
@@ -88,7 +129,7 @@ This log confirms the composable is working correctly. A stable parameter on the
88129

89130
```
90131
D/Recomposition: [Recomposition #2] UserProfile
91-
D/Recomposition: └─ user: User changed (User@abc123 → User@def456)
132+
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
92133
```
93134

94135
The `changed` label is the most important signal. It tells you this parameter's value is different from the last composition, which is the **reason** this composable recomposed. The arrow notation `(User@abc123 → User@def456)` shows the old and new identity hashes, confirming the value actually changed.
@@ -99,7 +140,7 @@ This is normal behavior. The parameter changed, so the composable recomposed to
99140

100141
```
101142
D/Recomposition: [Recomposition #1] UserCard (tag: user-card)
102-
D/Recomposition: ├─ user: MutableUser unstable (MutableUser@xyz789)
143+
D/Recomposition: ├─ [param] user: MutableUser unstable (MutableUser@xyz789)
103144
D/Recomposition: └─ Unstable parameters: [user]
104145
```
105146

@@ -109,9 +150,9 @@ The `unstable` label means the Compose compiler cannot guarantee this parameter
109150

110151
```
111152
D/Recomposition: [Recomposition #5] ProductList (tag: products)
112-
D/Recomposition: ├─ title: String stable (Products)
113-
D/Recomposition: ├─ count: Int changed (4 → 5)
114-
D/Recomposition: ├─ items: List<Product> unstable (List@abc)
153+
D/Recomposition: ├─ [param] title: String stable (Products)
154+
D/Recomposition: ├─ [param] count: Int changed (4 → 5)
155+
D/Recomposition: ├─ [param] items: List<Product> unstable (List@abc)
115156
D/Recomposition: └─ Unstable parameters: [items]
116157
```
117158

@@ -155,8 +196,8 @@ fun ProductCard(product: Product, onClick: () -> Unit) {
155196

156197
```
157198
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
158-
D/Recomposition: ├─ product: Product unstable (Product@abc)
159-
D/Recomposition: ├─ onClick: () -> Unit stable (Function@xyz)
199+
D/Recomposition: ├─ [param] product: Product unstable (Product@abc)
200+
D/Recomposition: ├─ [param] onClick: () -> Unit stable (Function@xyz)
160201
D/Recomposition: └─ Unstable parameters: [product]
161202
```
162203

@@ -176,8 +217,8 @@ data class Product(val name: String, val price: Double)
176217

177218
```
178219
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
179-
D/Recomposition: ├─ product: Product stable (Product@abc)
180-
D/Recomposition: └─ onClick: () -> Unit stable (Function@xyz)
220+
D/Recomposition: ├─ [param] product: Product stable (Product@abc)
221+
D/Recomposition: └─ [param] onClick: () -> Unit stable (Function@xyz)
181222
```
182223

183224
The `ProductCard` is now skippable. During scrolling, Compose will skip recomposing cards whose `product` and `onClick` values haven't changed, resulting in noticeably smoother performance.

docs/version-map.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ It is **strongly recommended to use the exact same Kotlin version** as this libr
66

77
| Stability Analyzer | Kotlin |
88
|--------------------|--------|
9-
| 0.7.2+ | 2.3.20 |
9+
| 0.7.3+ | 2.3.20 |
1010
| 0.6.5 ~ 0.7.0 | 2.3.0 |
1111
| 0.4.0 ~ 0.6.4 | 2.2.21 |
1212

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
5151

5252
# Maven publishing
5353
GROUP=com.github.skydoves
54-
VERSION_NAME=0.7.3-SNAPSHOT
54+
VERSION_NAME=0.7.3
5555

5656
POM_URL=https://github.com/skydoves/compose-stability-analyzer/
5757
POM_SCM_URL=https://github.com/skydoves/compose-stability-analyzer/

stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin {
4747

4848
// This version should match the version in gradle.properties
4949
// Update this when bumping the library version
50-
private const val VERSION = "0.7.3-SNAPSHOT"
50+
private const val VERSION = "0.7.3"
5151

5252
// Compiler option keys
5353
private const val OPTION_ENABLED = "enabled"

0 commit comments

Comments
 (0)