Skip to content

Commit 3e15ffc

Browse files
authored
Prepare for release 0.7.4 (#152)
1 parent ee173c8 commit 3e15ffc

10 files changed

Lines changed: 76 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ 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.4] - 2026-04-25
9+
10+
### Added
11+
- **Recomposition Profiler: Timing measurement** (Issue #89)
12+
- `@TraceRecomposition` now measures composable recomposition duration via `System.nanoTime()` IR injection
13+
- Duration displayed in logcat output: `[Recomposition #3] UserCard (2.30ms)`
14+
- Compiler plugin wraps composable body in try-finally for accurate timing even on exceptions
15+
- `RecompositionEvent.durationNanos` field added for custom logger consumption
16+
- KMP-compatible via `expect/actual currentNanoTime()` (Android/JVM supported, other platforms gracefully skip)
17+
- **Heatmap Tooltip** — Hover over recomposition count inlay in the editor to see:
18+
- Last recomposition duration
19+
- Parameter changes with old/new values
20+
- State changes with old/new values
21+
- Unstable parameter summary
22+
- Cumulative total recomposition count and duration
23+
- **Heatmap logcat parser updates** — Parses `[param]` and `[state]` prefixed lines and duration from log output
24+
25+
### Changed
26+
- **Shadow plugin upgraded to 9.0.0-beta12** for Gradle 9.x compatibility (plugin ID changed from `com.github.johnrengelman.shadow` to `com.gradleup.shadow`)
27+
828
## [0.7.3] - 2026-04-11
929

1030
### Added

README.md

Lines changed: 13 additions & 13 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.3" }
215+
stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version = "0.7.4" }
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.3+ | 2.3.20 |
237+
| 0.7.4+ | 2.3.20 |
238238
| 0.6.5~0.7.0 | 2.3.0 |
239239
| 0.4.0~0.6.4 | 2.2.21 |
240240

@@ -256,9 +256,9 @@ fun UserProfile(user: User) {
256256
That's it. When this composable recomposes, you'll see logs like:
257257

258258
```
259-
D/Recomposition: [Recomposition #1] UserProfile
259+
D/Recomposition: [Recomposition #1] UserProfile (0.12ms)
260260
D/Recomposition: └─ [param] user: User stable (User@abc123)
261-
D/Recomposition: [Recomposition #2] UserProfile
261+
D/Recomposition: [Recomposition #2] UserProfile (0.15ms)
262262
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
263263
```
264264

@@ -316,7 +316,7 @@ fun UserProfile(user: User) {
316316
Now logs include the tag:
317317

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

@@ -414,7 +414,7 @@ Let's understand what each log tells you:
414414
#### First Recomposition
415415

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

@@ -430,7 +430,7 @@ This log confirms the composable is working correctly. The parameter is stable a
430430
#### Parameter Changed
431431

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

@@ -444,15 +444,15 @@ This is normal behavior. The parameter changed, so the composable recomposed to
444444
#### Unstable Parameter
445445

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

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

454454
```
455-
D/Recomposition: [Recomposition #5] ProductList (tag: products)
455+
D/Recomposition: [Recomposition #5] ProductList (tag: products) (3.40ms)
456456
D/Recomposition: ├─ [param] title: String stable (Products)
457457
D/Recomposition: ├─ [param] count: Int changed (4 → 5)
458458
D/Recomposition: ├─ [param] items: List<Product> unstable (List@abc)
@@ -491,12 +491,12 @@ fun ProductCard(
491491
**Step 2: Run your app and check Logcat**
492492

493493
```
494-
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
494+
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card) (0.45ms)
495495
D/Recomposition: ├─ [param] product: Product unstable (Product@abc)
496496
D/Recomposition: ├─ [param] onClick: () -> Unit stable (Function@xyz)
497497
D/Recomposition: └─ Unstable parameters: [product]
498498
499-
D/Recomposition: [Recomposition #4] ProductCard (tag: product-card)
499+
D/Recomposition: [Recomposition #4] ProductCard (tag: product-card) (0.42ms)
500500
D/Recomposition: ├─ [param] product: Product unstable (Product@abc)
501501
D/Recomposition: ├─ [param] onClick: () -> Unit stable (Function@xyz)
502502
D/Recomposition: └─ Unstable parameters: [product]
@@ -536,7 +536,7 @@ data class Product(
536536
Run the app again and check Logcat:
537537

538538
```
539-
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
539+
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card) (0.45ms)
540540
D/Recomposition: ├─ [param] product: Product stable (Product@abc)
541541
D/Recomposition: └─ [param] onClick: () -> Unit stable (Function@xyz)
542542
@@ -568,7 +568,7 @@ fun CounterScreen(title: String) {
568568
After clicking the button:
569569

570570
```
571-
D/Recomposition: [Recomposition #2] CounterScreen
571+
D/Recomposition: [Recomposition #2] CounterScreen (1.20ms)
572572
D/Recomposition: ├─ [param] title: String stable (Counter)
573573
D/Recomposition: ├─ [state] counter: Int changed (0 → 1)
574574
D/Recomposition: └─ State changes: [counter]

compose-stability-analyzer-idea/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

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

5+
## [0.7.4] - 2026-04-25
6+
7+
### Added
8+
- **Heatmap Tooltip** — Hover over recomposition count inlay to see last recomposition details: duration, parameter/state changes, unstable params, and cumulative totals
9+
- **Duration display in heatmap** — Logcat parser now captures recomposition timing from `(X.XXms)` format
10+
- **`[param]` and `[state]` prefix parsing** — Updated logcat parser to handle new log format
11+
12+
### Changed
13+
- Removed manual `HintManager` tooltip in favor of `IdeTooltipManager` for correct positioning
14+
515
## [0.7.3] - 2026-04-11
616

717
### Fixed

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.4-SNAPSHOT"
29+
version = "0.7.4"
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.3+ | 2.3.20 |
48+
| 0.7.4+ | 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: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ fun UserProfile(user: User) {
2020
When this composable recomposes, detailed logs appear in Logcat showing the recomposition count, each parameter's stability status, and whether the parameter's value changed since the last composition.
2121

2222
```
23-
D/Recomposition: [Recomposition #1] UserProfile
23+
D/Recomposition: [Recomposition #1] UserProfile (0.12ms)
2424
D/Recomposition: └─ [param] user: User stable (User@abc123)
25-
D/Recomposition: [Recomposition #2] UserProfile
25+
D/Recomposition: [Recomposition #2] UserProfile (0.15ms)
2626
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
2727
```
2828

@@ -45,7 +45,7 @@ fun UserProfile(user: User) {
4545
Logs now include the tag, making it easy to filter in Logcat:
4646

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

@@ -94,7 +94,7 @@ fun CounterScreen(title: String) {
9494
After clicking the button, the log now shows both parameter and state information:
9595

9696
```
97-
D/Recomposition: [Recomposition #2] CounterScreen
97+
D/Recomposition: [Recomposition #2] CounterScreen (1.20ms)
9898
D/Recomposition: ├─ [param] title: String stable (Counter)
9999
D/Recomposition: ├─ [state] counter: Int changed (0 → 1)
100100
D/Recomposition: └─ State changes: [counter]
@@ -110,14 +110,31 @@ The `[param]` prefix identifies parameter tracking entries, while `[state]` iden
110110

111111
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.
112112

113+
## Recomposition Duration
114+
115+
Every `@TraceRecomposition` composable automatically measures how long each recomposition takes. The duration appears in the log header:
116+
117+
```
118+
D/Recomposition: [Recomposition #3] UserCard (2.30ms)
119+
D/Recomposition: └─ [param] user: User changed (User@abc → User@def)
120+
```
121+
122+
The `(2.30ms)` shows how long the composable body took to execute during this recomposition. This helps identify which composables are expensive, not just which ones recompose frequently.
123+
124+
Duration data is also available in `RecompositionEvent.durationNanos` for custom loggers, and is displayed in the IDE's heatmap tooltip when hovering over the recomposition count inlay.
125+
126+
!!! note "Platform support"
127+
128+
Duration measurement uses `System.nanoTime()` and is available on Android and JVM. On other KMP platforms (iOS, JS, WASM), timing is gracefully skipped and `durationNanos` will be `0`.
129+
113130
## Reading the Logs
114131

115132
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.
116133

117134
### First Recomposition
118135

119136
```
120-
D/Recomposition: [Recomposition #1] UserProfile
137+
D/Recomposition: [Recomposition #1] UserProfile (0.12ms)
121138
D/Recomposition: └─ [param] user: User stable (User@abc123)
122139
```
123140

@@ -128,7 +145,7 @@ This log confirms the composable is working correctly. A stable parameter on the
128145
### Parameter Changed
129146

130147
```
131-
D/Recomposition: [Recomposition #2] UserProfile
148+
D/Recomposition: [Recomposition #2] UserProfile (0.15ms)
132149
D/Recomposition: └─ [param] user: User changed (User@abc123 → User@def456)
133150
```
134151

@@ -139,7 +156,7 @@ This is normal behavior. The parameter changed, so the composable recomposed to
139156
### Unstable Parameter
140157

141158
```
142-
D/Recomposition: [Recomposition #1] UserCard (tag: user-card)
159+
D/Recomposition: [Recomposition #1] UserCard (tag: user-card) (0.25ms)
143160
D/Recomposition: ├─ [param] user: MutableUser unstable (MutableUser@xyz789)
144161
D/Recomposition: └─ Unstable parameters: [user]
145162
```
@@ -149,7 +166,7 @@ The `unstable` label means the Compose compiler cannot guarantee this parameter
149166
### Multiple Parameters (Mixed Stability)
150167

151168
```
152-
D/Recomposition: [Recomposition #5] ProductList (tag: products)
169+
D/Recomposition: [Recomposition #5] ProductList (tag: products) (3.40ms)
153170
D/Recomposition: ├─ [param] title: String stable (Products)
154171
D/Recomposition: ├─ [param] count: Int changed (4 → 5)
155172
D/Recomposition: ├─ [param] items: List<Product> unstable (List@abc)
@@ -195,7 +212,7 @@ fun ProductCard(product: Product, onClick: () -> Unit) {
195212
**Run the app and check Logcat.** Filter by `product-card` to see only this composable's events. After scrolling through the list, you see logs appearing rapidly:
196213

197214
```
198-
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
215+
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card) (0.45ms)
199216
D/Recomposition: ├─ [param] product: Product unstable (Product@abc)
200217
D/Recomposition: ├─ [param] onClick: () -> Unit stable (Function@xyz)
201218
D/Recomposition: └─ Unstable parameters: [product]
@@ -216,7 +233,7 @@ data class Product(val name: String, val price: Double)
216233
**Verify the fix** by running the app again. The logs now show stable parameters:
217234

218235
```
219-
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card)
236+
D/Recomposition: [Recomposition #3] ProductCard (tag: product-card) (0.45ms)
220237
D/Recomposition: ├─ [param] product: Product stable (Product@abc)
221238
D/Recomposition: └─ [param] onClick: () -> Unit stable (Function@xyz)
222239
```

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.3+ | 2.3.20 |
9+
| 0.7.4+ | 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
@@ -48,7 +48,7 @@ kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
4848

4949
# Maven publishing
5050
GROUP=com.github.skydoves
51-
VERSION_NAME=0.7.4-SNAPSHOT
51+
VERSION_NAME=0.7.4
5252

5353
POM_URL=https://github.com/skydoves/compose-stability-analyzer/
5454
POM_SCM_URL=https://github.com/skydoves/compose-stability-analyzer/

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ androidGradlePlugin = "8.13.1"
1313
androidxActivity = "1.11.0"
1414
androidxComposeBom = "2025.12.00"
1515
jetbrains-compose = "1.9.3"
16-
compose-stability-analyzer = "0.7.4-SNAPSHOT"
16+
compose-stability-analyzer = "0.7.3"
1717
runtimeAnnotation = "1.9.0"
1818
spotless = "6.21.0"
1919
shadow = "9.0.0-beta12"

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.4-SNAPSHOT"
50+
private const val VERSION = "0.7.4"
5151

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

0 commit comments

Comments
 (0)