
Instruments @Composable functions with lightweight tracking to monitor recomposition rates against per-composable budgets, detect violations, and report via IDE performance cockpit, CLI, and logs.
Rebound helps Android and Compose Multiplatform teams catch recomposition regressions before they ship.
It instruments every @Composable function, applies a budget based on what that composable does, and flags the ones that start recomposing too often. You see the result in Android Studio, in the CLI, or in logcat. Zero config required beyond applying the Gradle plugin.
check, snapshot, diff, and watch workflows@ReboundBudget annotation — override the inferred budget class for any composabledoctor, check, snapshot, summary, watch, ping commands over ADB socket1. Apply the Gradle plugin
// settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}// build.gradle.kts (app module)
plugins {
id("io.github.aldefy.rebound") version "0.2.2"
}2. Configure (optional)
rebound {
enabled.set(true) // default: true
debugOnly.set(true) // default: true — only instruments debug builds
}3. Run your app
Rebound auto-installs on first composition. Violations appear in logcat:
W/Rebound: [VIOLATION] MyScreen — 8 recomp/s (budget: 3, class: SCREEN)
4. Prove the runtime path
Run the CLI while the debug app is open:
./rebound-cli.sh doctor # Diagnoses tools, device, socket, and ADB forwarding
./rebound-cli.sh check # Proves ping + snapshot + summary data is flowing
./rebound-cli.sh summary # Shareable top recomposition risks for PRs, agents, or CIdoctor tells you why setup is broken. check proves the app is reachable and emitting JSON metrics. summary is the compact output to paste into reviews or automation.
Connect the IDE plugin for richer live output.
The repo includes a sample app that intentionally violates a screen-level recomposition budget by reading a 60fps ticker at BudgetViolationScreen scope.
./gradlew :sample:installDebug
adb shell am start -n io.aldefy.rebound.sample/.MainActivity
./rebound-cli.sh doctor
./rebound-cli.sh check
./rebound-cli.sh summaryExpected signal: BudgetViolationScreen appears over its SCREEN budget. Tap Fixed in the sample app, wait a few seconds, then rerun summary to see the fast state isolated in an ANIMATED child.
The compiler plugin injects ReboundTracker.onCompositionEnter/Exit calls into every @Composable function at the IR level. The runtime tracks rates per sliding window and reports violations. The IDE plugin and CLI connect via LocalServerSocket("rebound") forwarded through ADB (Android) or via a WebSocket relay (iOS physical devices).
Platform support: The compiler plugin instruments all KMP targets. Android uses
LocalServerSocket+ ADB forward. iOS simulator uses a direct TCP server on:18462. iOS physical devices connect outbound to a Mac-side WebSocket relay via Bonjour auto-discovery, with console logging as fallback.
| Module | Description |
|---|---|
rebound-runtime |
KMP runtime library (Android, iOS, JVM). Full transport on all platforms. |
rebound-compiler |
Kotlin compiler plugin for Kotlin 2.0.x-2.1.x |
rebound-compiler-k2 |
Kotlin compiler plugin for Kotlin 2.2+ |
rebound-gradle |
Gradle plugin — auto-wires compiler + runtime, selects correct artifact |
rebound-ide |
Android Studio plugin — 5-tab performance cockpit with editor integration |
tools/rebound-relay |
Mac relay server — bridges iOS physical devices to CLI/IDE via WebSocket |
sample |
Android sample app with a runnable recomposition-budget violation demo |
rebound {
enabled.set(true) // Master switch — set false to disable all instrumentation
debugOnly.set(true) // Only instrument debug builds (release builds get no overhead)
}Runtime toggles:
ReboundTracker.enabled = true // Master on/off at runtime
ReboundTracker.logCompositions = false // Per-composition logcat (throttled 1/s per composable)Each composable is auto-classified by IR heuristics:
| Budget Class | Rate/sec | Heuristic |
|---|---|---|
SCREEN |
3 | Name contains Screen or Page
|
CONTAINER |
10 | Has child @Composable calls |
INTERACTIVE |
30 | Default for unclassified |
LIST_ITEM |
60 | Inside LazyColumn/LazyRow/LazyGrid
|
ANIMATED |
120 | Calls animate*/Transition/Animation APIs |
LEAF |
5 | No child @Composable calls |
Screen or Page.derivedStateOf or move reads into a smaller scope.@Composable calls — Column, Row, Box, Scaffold content slots.@Composable calls in its body.animate*, Transition, Animation, or Animatable APIs.@Composable calls — Text(), Icon(), Image().@Composable calls in the function body.| Color | Condition | Status |
|---|---|---|
| Red | rate > budget | OVER |
| Yellow | rate > 70% of budget | NEAR |
| Green | rate <= 70% of budget | OK |
| Gray | not actively recomposing | — |
| Interaction State | Multiplier | Effect |
|---|---|---|
| IDLE | 1x | Normal budgets |
| SCROLLING | 2x | Budgets doubled during scroll |
| ANIMATING | 1.5x | Budgets increased during animation |
| USER_INPUT | 1.5x | Budgets increased during input |
// This composable uses tilt sensor — it's not a leaf, it's animated
@ReboundBudget(BudgetClass.ANIMATED)
@Composable
fun TiltDrivenSticker(offset: Offset) { ... }The Android Studio plugin (targets 2024.2.1.3+) provides a 5-tab performance cockpit, editor integration, and session persistence. Configure via Preferences > Tools > Rebound.
Monitor — Live composable tree with sparkline rate history per node. Scrolling event log at the bottom shows recomposition events, violations, and state changes in real time.
Hot Spots — Sortable flat table of all tracked composables, ranked by severity (OVER > NEAR > OK). Summary card at the top shows violation/warning/OK counts at a glance. Click any row to jump to source.
Timeline — Composable x time heatmap. Each cell is colored green/yellow/red based on budget status at that moment. Scroll back up to 60 minutes. Useful for correlating recomposition spikes with user interactions.
Stability — Parameter stability matrix showing SAME/DIFFERENT/STATIC/UNCERTAIN status per parameter for each composable. Cascade impact tree visualizes how unstable parameters propagate recompositions through the hierarchy.
History — Saved sessions stored in .rebound/sessions/. Each session is VCS-tagged with branch name and commit hash. Side-by-side comparison view for before/after regression analysis.
@Composable function declarations, reflecting live budget status.> 12/s | budget: 8/s | OVER | skip: 45%.Rebound: 45 composables | 3 violations.The plugin connects via adb forward tcp:18462 localabstract:rebound. Install from the rebound-ide build output.
The CLI auto-detects the connection path: direct TCP (iOS simulator or relay), ADB forward (Android), or devicectl console (iOS physical without relay).
./rebound-cli.sh doctor # Diagnose tools, device, socket, and ADB forwarding
./rebound-cli.sh check # Ping + snapshot + summary sanity check
./rebound-cli.sh summary # Top 10 composables by recomposition rate
./rebound-cli.sh snapshot # Full JSON metrics for all tracked composables
./rebound-cli.sh watch # Live updates every 1 second
./rebound-cli.sh ping # Health check → "pong"Or query directly:
echo "snapshot" | nc localhost 18462For iOS physical devices, the runtime connects outbound to a Mac-side relay via WebSocket. The device discovers the relay automatically via Bonjour.
# Build the relay (one-time)
./tools/build-relay.sh
# Start the relay on your Mac
./tools/rebound-relay
# → TCP :18462 (CLI/IDE), WebSocket :18463 (devices), Bonjour: _rebound._tcp
# Now run your app on the physical device (same WiFi network)
# The device auto-discovers the relay and connects
./rebound-cli.sh snapshot # works transparently through relayOverride Bonjour discovery with an env var if needed (e.g., different subnet):
REBOUND_RELAY_HOST=192.168.1.100:18463
| Kotlin Version | Compiler Artifact | Status |
|---|---|---|
| 2.0.x | rebound-compiler |
Stable |
| 2.1.x | rebound-compiler |
Stable |
| 2.2.x | rebound-compiler-kotlin-2.2 |
Stable |
| 2.3.x | rebound-compiler-kotlin-2.3 |
Stable |
The Gradle plugin auto-detects your project's Kotlin version and selects the correct compiler artifact. No manual configuration needed.
| Platform | Transport | Status |
|---|---|---|
| Android (device/emulator) |
LocalServerSocket + ADB forward |
Stable |
| iOS Simulator | Direct TCP on :18462
|
Stable |
| iOS Physical Device | WebSocket → Mac relay (Bonjour auto-discovery) | Stable |
| iOS Physical Device (no relay) | Console logging via devicectl
|
Stable (one-way) |
| JVM/Desktop | In-memory (no transport) | Metrics collected, no export |
Why multiple artifacts? Kotlin's compiler plugin IR API changes between minor versions. Each artifact is compiled against the matching kotlin-compiler-embeddable to ensure binary compatibility.
See roadmap.md for the full iOS roadmap.
Make your AI coding tool understand Compose recomposition performance. The rebound-skill repo contains markdown-based instructions that any AI assistant can consume.
What it covers: Budget classes, violation diagnosis, skip rate analysis, stability optimization, CLI usage, IDE plugin workflow — all backed by the actual Rebound codebase.
Supports: Claude Code, Gemini CLI, Gemini (Android Studio), Cursor, Copilot, Codex, Windsurf, Amazon Q, and any markdown-consuming AI tool.
See aldefy/rebound-skill for setup instructions.
Full documentation at aldefy.github.io/compose-rebound.
# Build all modules
./gradlew build
# Build IDE plugin
./gradlew :rebound-ide:buildPlugin
# Output: rebound-ide/build/distributions/rebound-ide-0.2.2.zip
# Run sample app
./gradlew :sample:installDebugCopyright 2025 Adit Lal
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Rebound helps Android and Compose Multiplatform teams catch recomposition regressions before they ship.
It instruments every @Composable function, applies a budget based on what that composable does, and flags the ones that start recomposing too often. You see the result in Android Studio, in the CLI, or in logcat. Zero config required beyond applying the Gradle plugin.
check, snapshot, diff, and watch workflows@ReboundBudget annotation — override the inferred budget class for any composabledoctor, check, snapshot, summary, watch, ping commands over ADB socket1. Apply the Gradle plugin
// settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}// build.gradle.kts (app module)
plugins {
id("io.github.aldefy.rebound") version "0.2.2"
}2. Configure (optional)
rebound {
enabled.set(true) // default: true
debugOnly.set(true) // default: true — only instruments debug builds
}3. Run your app
Rebound auto-installs on first composition. Violations appear in logcat:
W/Rebound: [VIOLATION] MyScreen — 8 recomp/s (budget: 3, class: SCREEN)
4. Prove the runtime path
Run the CLI while the debug app is open:
./rebound-cli.sh doctor # Diagnoses tools, device, socket, and ADB forwarding
./rebound-cli.sh check # Proves ping + snapshot + summary data is flowing
./rebound-cli.sh summary # Shareable top recomposition risks for PRs, agents, or CIdoctor tells you why setup is broken. check proves the app is reachable and emitting JSON metrics. summary is the compact output to paste into reviews or automation.
Connect the IDE plugin for richer live output.
The repo includes a sample app that intentionally violates a screen-level recomposition budget by reading a 60fps ticker at BudgetViolationScreen scope.
./gradlew :sample:installDebug
adb shell am start -n io.aldefy.rebound.sample/.MainActivity
./rebound-cli.sh doctor
./rebound-cli.sh check
./rebound-cli.sh summaryExpected signal: BudgetViolationScreen appears over its SCREEN budget. Tap Fixed in the sample app, wait a few seconds, then rerun summary to see the fast state isolated in an ANIMATED child.
The compiler plugin injects ReboundTracker.onCompositionEnter/Exit calls into every @Composable function at the IR level. The runtime tracks rates per sliding window and reports violations. The IDE plugin and CLI connect via LocalServerSocket("rebound") forwarded through ADB (Android) or via a WebSocket relay (iOS physical devices).
Platform support: The compiler plugin instruments all KMP targets. Android uses
LocalServerSocket+ ADB forward. iOS simulator uses a direct TCP server on:18462. iOS physical devices connect outbound to a Mac-side WebSocket relay via Bonjour auto-discovery, with console logging as fallback.
| Module | Description |
|---|---|
rebound-runtime |
KMP runtime library (Android, iOS, JVM). Full transport on all platforms. |
rebound-compiler |
Kotlin compiler plugin for Kotlin 2.0.x-2.1.x |
rebound-compiler-k2 |
Kotlin compiler plugin for Kotlin 2.2+ |
rebound-gradle |
Gradle plugin — auto-wires compiler + runtime, selects correct artifact |
rebound-ide |
Android Studio plugin — 5-tab performance cockpit with editor integration |
tools/rebound-relay |
Mac relay server — bridges iOS physical devices to CLI/IDE via WebSocket |
sample |
Android sample app with a runnable recomposition-budget violation demo |
rebound {
enabled.set(true) // Master switch — set false to disable all instrumentation
debugOnly.set(true) // Only instrument debug builds (release builds get no overhead)
}Runtime toggles:
ReboundTracker.enabled = true // Master on/off at runtime
ReboundTracker.logCompositions = false // Per-composition logcat (throttled 1/s per composable)Each composable is auto-classified by IR heuristics:
| Budget Class | Rate/sec | Heuristic |
|---|---|---|
SCREEN |
3 | Name contains Screen or Page
|
CONTAINER |
10 | Has child @Composable calls |
INTERACTIVE |
30 | Default for unclassified |
LIST_ITEM |
60 | Inside LazyColumn/LazyRow/LazyGrid
|
ANIMATED |
120 | Calls animate*/Transition/Animation APIs |
LEAF |
5 | No child @Composable calls |
Screen or Page.derivedStateOf or move reads into a smaller scope.@Composable calls — Column, Row, Box, Scaffold content slots.@Composable calls in its body.animate*, Transition, Animation, or Animatable APIs.@Composable calls — Text(), Icon(), Image().@Composable calls in the function body.| Color | Condition | Status |
|---|---|---|
| Red | rate > budget | OVER |
| Yellow | rate > 70% of budget | NEAR |
| Green | rate <= 70% of budget | OK |
| Gray | not actively recomposing | — |
| Interaction State | Multiplier | Effect |
|---|---|---|
| IDLE | 1x | Normal budgets |
| SCROLLING | 2x | Budgets doubled during scroll |
| ANIMATING | 1.5x | Budgets increased during animation |
| USER_INPUT | 1.5x | Budgets increased during input |
// This composable uses tilt sensor — it's not a leaf, it's animated
@ReboundBudget(BudgetClass.ANIMATED)
@Composable
fun TiltDrivenSticker(offset: Offset) { ... }The Android Studio plugin (targets 2024.2.1.3+) provides a 5-tab performance cockpit, editor integration, and session persistence. Configure via Preferences > Tools > Rebound.
Monitor — Live composable tree with sparkline rate history per node. Scrolling event log at the bottom shows recomposition events, violations, and state changes in real time.
Hot Spots — Sortable flat table of all tracked composables, ranked by severity (OVER > NEAR > OK). Summary card at the top shows violation/warning/OK counts at a glance. Click any row to jump to source.
Timeline — Composable x time heatmap. Each cell is colored green/yellow/red based on budget status at that moment. Scroll back up to 60 minutes. Useful for correlating recomposition spikes with user interactions.
Stability — Parameter stability matrix showing SAME/DIFFERENT/STATIC/UNCERTAIN status per parameter for each composable. Cascade impact tree visualizes how unstable parameters propagate recompositions through the hierarchy.
History — Saved sessions stored in .rebound/sessions/. Each session is VCS-tagged with branch name and commit hash. Side-by-side comparison view for before/after regression analysis.
@Composable function declarations, reflecting live budget status.> 12/s | budget: 8/s | OVER | skip: 45%.Rebound: 45 composables | 3 violations.The plugin connects via adb forward tcp:18462 localabstract:rebound. Install from the rebound-ide build output.
The CLI auto-detects the connection path: direct TCP (iOS simulator or relay), ADB forward (Android), or devicectl console (iOS physical without relay).
./rebound-cli.sh doctor # Diagnose tools, device, socket, and ADB forwarding
./rebound-cli.sh check # Ping + snapshot + summary sanity check
./rebound-cli.sh summary # Top 10 composables by recomposition rate
./rebound-cli.sh snapshot # Full JSON metrics for all tracked composables
./rebound-cli.sh watch # Live updates every 1 second
./rebound-cli.sh ping # Health check → "pong"Or query directly:
echo "snapshot" | nc localhost 18462For iOS physical devices, the runtime connects outbound to a Mac-side relay via WebSocket. The device discovers the relay automatically via Bonjour.
# Build the relay (one-time)
./tools/build-relay.sh
# Start the relay on your Mac
./tools/rebound-relay
# → TCP :18462 (CLI/IDE), WebSocket :18463 (devices), Bonjour: _rebound._tcp
# Now run your app on the physical device (same WiFi network)
# The device auto-discovers the relay and connects
./rebound-cli.sh snapshot # works transparently through relayOverride Bonjour discovery with an env var if needed (e.g., different subnet):
REBOUND_RELAY_HOST=192.168.1.100:18463
| Kotlin Version | Compiler Artifact | Status |
|---|---|---|
| 2.0.x | rebound-compiler |
Stable |
| 2.1.x | rebound-compiler |
Stable |
| 2.2.x | rebound-compiler-kotlin-2.2 |
Stable |
| 2.3.x | rebound-compiler-kotlin-2.3 |
Stable |
The Gradle plugin auto-detects your project's Kotlin version and selects the correct compiler artifact. No manual configuration needed.
| Platform | Transport | Status |
|---|---|---|
| Android (device/emulator) |
LocalServerSocket + ADB forward |
Stable |
| iOS Simulator | Direct TCP on :18462
|
Stable |
| iOS Physical Device | WebSocket → Mac relay (Bonjour auto-discovery) | Stable |
| iOS Physical Device (no relay) | Console logging via devicectl
|
Stable (one-way) |
| JVM/Desktop | In-memory (no transport) | Metrics collected, no export |
Why multiple artifacts? Kotlin's compiler plugin IR API changes between minor versions. Each artifact is compiled against the matching kotlin-compiler-embeddable to ensure binary compatibility.
See roadmap.md for the full iOS roadmap.
Make your AI coding tool understand Compose recomposition performance. The rebound-skill repo contains markdown-based instructions that any AI assistant can consume.
What it covers: Budget classes, violation diagnosis, skip rate analysis, stability optimization, CLI usage, IDE plugin workflow — all backed by the actual Rebound codebase.
Supports: Claude Code, Gemini CLI, Gemini (Android Studio), Cursor, Copilot, Codex, Windsurf, Amazon Q, and any markdown-consuming AI tool.
See aldefy/rebound-skill for setup instructions.
Full documentation at aldefy.github.io/compose-rebound.
# Build all modules
./gradlew build
# Build IDE plugin
./gradlew :rebound-ide:buildPlugin
# Output: rebound-ide/build/distributions/rebound-ide-0.2.2.zip
# Run sample app
./gradlew :sample:installDebugCopyright 2025 Adit Lal
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.