{"id":34585645,"url":"https://github.com/skydoves/compose-stability-analyzer","last_synced_at":"2026-02-13T11:03:12.949Z","repository":{"id":322007005,"uuid":"1087919806","full_name":"skydoves/compose-stability-analyzer","owner":"skydoves","description":"🦄 Real-time analysis of Jetpack Compose composable functions' stability directly within Android Studio or IntelliJ.","archived":false,"fork":false,"pushed_at":"2026-02-10T02:57:41.000Z","size":3581,"stargazers_count":1269,"open_issues_count":10,"forks_count":27,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-02-10T07:37:03.768Z","etag":null,"topics":["android","compose","intellij-plugin","jetpack-compose","kotlin-compiler-plugin","skydoves","stability"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skydoves.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"skydoves","custom":["https://www.android.skydoves.me/","https://kotlin.skydoves.me/","https://github.com/doveletter"]}},"created_at":"2025-11-01T23:05:46.000Z","updated_at":"2026-02-10T02:57:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/skydoves/compose-stability-analyzer","commit_stats":null,"previous_names":["skydoves/compose-stability-analyzer"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/skydoves/compose-stability-analyzer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skydoves%2Fcompose-stability-analyzer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skydoves%2Fcompose-stability-analyzer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skydoves%2Fcompose-stability-analyzer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skydoves%2Fcompose-stability-analyzer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skydoves","download_url":"https://codeload.github.com/skydoves/compose-stability-analyzer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skydoves%2Fcompose-stability-analyzer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29403269,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T06:24:03.484Z","status":"ssl_error","status_checked_at":"2026-02-13T06:23:12.830Z","response_time":78,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","compose","intellij-plugin","jetpack-compose","kotlin-compiler-plugin","skydoves","stability"],"created_at":"2025-12-24T10:30:33.726Z","updated_at":"2026-02-13T11:03:12.941Z","avatar_url":"https://github.com/skydoves.png","language":"Kotlin","readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/user-attachments/assets/d3b1b1ae-d4f4-4ab6-8067-376f74721186\" width=\"120px\"/\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eCompose Stability Analyzer\u003c/h1\u003e\u003c/br\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/badge/License-Apache%202.0-blue.svg\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://android-arsenal.com/api?level=21\"\u003e\u003cimg alt=\"API\" src=\"https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/skydoves/compose-stability-analyzer/actions\"\u003e\u003cimg alt=\"Build Status\" src=\"https://github.com/skydoves/compose-stability-analyzer/workflows/Android%20CI/badge.svg\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/skydoves\"\u003e\u003cimg alt=\"Profile\" src=\"https://skydoves.github.io/badges/skydoves.svg\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/doveletter\"\u003e\u003cimg alt=\"Profile\" src=\"https://skydoves.github.io/badges/dove-letter.svg\"/\u003e\u003c/a\u003e\u003cbr\u003e\n\u003c/p\u003e\n\n![preview](art/preview0.png)\n\nCompose Stability Analyzer provides real-time analysis of your Jetpack Compose composable functions' stability directly within Android Studio or IntelliJ. It helps you understand why a composable function is stable or unstable, and offers detailed insights through recomposition tracing and logging. \n\nAdditionally, you can trace the reason of your composable function is triggered recomposition with a `TraceRecomposition` annotation, and export stability compatibility reports using Gradle tasks for reviewing the new stability changes.\n\nYou can change the colors used for stability indicators to match your IDE theme, enabling Strong Skipping mode for analyzing, visual indicators (showing gutter icons, warnings, inline hints), change parameter hint colors, enabling analysis in test source sets, set a stability configuration file, add ignored type patterns to exclude from the stability analysis.\n\n## 💝 Sponsors\n\nThe sponsors listed below made it possible for this project to be released as open source. Many thanks to all of them for their support!\n\n\u003ca href=\"https://github.com/GetStream/Vision-Agents/?utm_source=github\u0026utm_medium=devrel\u0026utm_campaign=jaewoong-github\"\u003e\u003cimg alt=\"Profile\" src=\"art/logo-vision-agents.png\" width=\"300\"/\u003e\u003c/a\u003e\n\n**[Vision Agents (GitHub)](https://github.com/GetStream/Vision-Agents/?utm_source=github\u0026utm_medium=devrel\u0026utm_campaign=jaewoong-github)** is an open-source Video AI framework for building real-time voice and video applications. The framework is edge/transport agnostic, meaning developers can also bring any edge layer they like.\n\n\u003ca href=\"https://coderabbit.link/Jaewoong\" target=\"_blank\"\u003e \u003cimg width=\"300\" alt=\"coderabbit\" src=\"art/logo-coderabbit.png\" /\u003e\u003c/a\u003e\n\n**[CodeRabbit](https://coderabbit.link/Jaewoong)** is an AI-powered code review platform that integrates directly into pull-request workflows and IDEs, examining code changes in context and suggesting improvements.\n\n\u003ca href=\"https://firebender.com/?utm_source=skydoves\"\u003e\u003cimg alt=\"Profile\" src=\"art/logo-firebender.png\" width=\"300\"/\u003e\u003c/a\u003e\n\n**[Firebender](https://firebender.com/?utm_source=skydoves)** is the most powerful AI coding agent in Android Studio. It can create entire compose UIs from Figma links, generate UML diagrams, and even understand your voice input.\n\n## Compose Stability Analyzer Plugin\n\nThe Compose Stability Analyzer IntelliJ Plugin brings **visual stability analysis** directly into your IDE (Android Studio), helping you identify and fix performance issues while you code. Instead of waiting for runtime or build-time reports, you get instant feedback right in Android Studio or IntelliJ IDEA.\n\nThis plugin provides real-time visual feedback about your composables' stability through six main features:\n\n- **1. Gutter Icons**: Colored dots in the editor margin showing if a composable is skippable.\n- **2. Hover Tooltips**: Detailed stability information when you hover over composable functions. It also provides the reasons: why it's stable or unstable.\n- **3. Inline Parameter Hints**: Badges next to parameters showing their stability status.\n- **4. Code Inspections**: Quick fixes and warnings for unstable composables.\n- **5. Recomposition Cascade**: Visualize downstream composables affected by recomposition from any `@Composable` function.\n- **6. Live Recomposition Heatmap**: Real-time recomposition counts from a connected device overlaid directly in the editor.\n\n\u003e **Note**: You don’t need to make every composable function skippable or all parameters stable, these are not direct indicators of performance optimization. The goal of this plugin isn’t to encourage over-focusing on stability, but rather to help you explore how Compose’s stability mechanisms work and use them as tools for examining and debugging composables that may have performance issues. For more information, check out [Compose Stability Analyzer: Real-Time Stability Insights for Jetpack Compose](https://medium.com/proandroiddev/compose-stability-analyzer-real-time-stability-insights-for-jetpack-compose-1399924a0a64).\n\n### How to Install in Android Studio\n\nYou can download the Compose Stability Analyzer Plugin with the steps below:\n\nOpen **Android Studio** \u003e **Settings** (or **Preferences**) \u003e **Plugins** \u003e Marketplace \u003e Compose Stability Analyzer \u003e Install\n\n![preview](art/preview3.png)\n\nIf you see gutter icons and tooltips, you're all set! 🎉\n\n\u003e **Note**: For now, the plugin is under review on the JetBrains Plugin Marketplace. Once the review is complete, you’ll be able to download it directly from your IDE’s marketplace.\n\n### Stability Mark for Composable Functions\n\nGutter icons appear in the left margin of your editor, giving you instant visual feedback on your composable functions:\n\n![preview](art/preview1.png)\n\nThis is the fastest way to spot performance problems. Just glance at the left margin, if you see red dots, those composables need attention.\n\nAlso, when you hover your mouse over a composable function name, a detailed tooltip appears showing:\n\n- Whether it's skippable or restartable\n- How many parameters are stable vs. unstable\n- Which specific parameters are causing instability\n- Additional context about receivers (if any)\n\nThis gives you the **why** behind the gutter icon color. You don't just see that a composable is unstable, you see exactly which parameters are the problem.\n\n### Inline Parameter Hints\n\nInline hints are small badges that appear right next to parameter types, showing the stability of each individual parameter. This is the most detailed level of feedback, you see stability information for every single parameter at a glance.\n\n![preview](art/preview2.png)\n\n### Code Inspections\n\nCode inspections go beyond visual indicators, they actively suggest improvements. When you have an unstable composable, the plugin can:\n\n1. **Highlight the issue** with a warning underline\n2. **Suggest quick fixes** via Alt+Enter menu\n3. **Add @TraceRecomposition** to help you debug recompositions\n4. **Provide suppression options** if the instability is intentional\n\nThis is like having an automated code review for Compose performance. The plugin doesn't just tell you about problems, it helps you fix them.\n\n\u003e **Troubleshooting**: If the plugin doesn't appear to work, check **Settings → Tools → Compose Stability Analyzer** and make sure **Enable stability checks** is turned on.\n\n### Stability Explorer\n\nThis JetBrains IDE plugin provides a Stability Explorer directly in your IDE, allowing you to visually trace which composable functions are skippable or non-skippable, and identify which parameters are stable or unstable within a specific package hierarchy. \n\n![preview](art/preview7.png)\n\nYou can enable this explorer with the steps below:\n\n1. Install the [Compose Stability Analyzer Gradle plugin](https://github.com/skydoves/compose-stability-analyzer?tab=readme-ov-file#including-in-your-project)\n2. On your IDE, go to **View** -\u003e **Tool Windows** -\u003e **Compose Stability Analyzer**, then you will see the icon on the right side of your Android Studio. Click the icon then you'll see a panel.\n3. Clean \u0026 build your project, and click the refresh button on the panel.\n\n### Recomposition Cascade\n\nThe Recomposition Cascade visualizer lets you trace the **downstream impact** of instability in your composable functions. Right-click any `@Composable` function in the editor and select **\"Analyze Recomposition Cascade\"** to see a tree of all downstream composables that would be affected by recomposition.\n\n![cascade](art/cascade.png)\n\nThis helps you answer questions like:\n- \"If this composable recomposes, how many downstream composables are affected?\"\n- \"Which parts of my composable tree are skippable and which are not?\"\n- \"Where should I focus my stability optimization efforts?\"\n\nThe cascade tree shows:\n- Each downstream composable with its stability status (skippable vs. non-skippable)\n- Summary statistics: total downstream count, skippable count, unskippable count, and max depth\n- Double-click any node to navigate directly to its source code\n- Cycle detection and depth limits prevent infinite analysis in recursive call graphs\n\n### Live Recomposition Heatmap\n\nThe Live Recomposition Heatmap bridges **runtime behavior** with your IDE. It reads actual `@TraceRecomposition` events from a connected device via ADB logcat and overlays real recomposition counts directly above composable functions in the editor, color-coded by severity.\n\n![heatmap](art/heatmap.gif)\n\n**How to use:**\n\n1. Add `@TraceRecomposition` annotations to the composables you want to monitor\n2. Enable `ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)` in your Application class\n3. Connect your device/emulator and run the app\n4. Click the **Start Recomposition Heatmap** button in the tool window title bar (or use **Code menu \u003e Toggle Recomposition Heatmap**)\n5. Interact with your app -- recomposition counts appear above composables in the editor in real time\n\n**Color-coded severity:**\n- **Green**: Low recomposition count (\u003c 10 by default)\n- **Yellow**: Medium recomposition count (10-50)\n- **Red**: High recomposition count (50+)\n\n**Click-to-inspect:** Click on any recomposition count in the editor to open the **Heatmap** tab in the tool window, showing detailed recomposition event logs with parameter change history for that composable.\n\n\u003e **Note**: The heatmap requires a connected device running your app with `@TraceRecomposition` composables. Severity thresholds are configurable in **Settings \u003e Tools \u003e Compose Stability Analyzer**.\n\n### Plugin Customization\n\nYou can change the colors used for stability indicators to match your IDE theme, enabling Strong Skipping mode for analyzing, visual indicators (showing gutter icons, warnings, inline hints), change parameter hint colors, enabling analysis in test source sets, set a stability configuration file, add ignored type patterns to exclude from the stability analysis.\n\nYou can change the configuration on the way below:\n\n**Settings → Tools → Compose Stability Analyzer**\n\n![preview](art/preview4.png)\n![preview](art/preview5.png)\n\n#### Stability Configuration File\n\nThe Compose Stability Analyzer allows you to mark your own custom types as stable, even if they don't have `@Stable` or `@Immutable` annotations. This is useful when:\n\n- You have immutable data classes from third-party libraries that aren't annotated\n- You're using code generation tools that produce stable types\n- You want to treat certain types as stable without modifying their source code\n- You have legacy code that you know is stable but can't easily refactor\n\n**Setting up the configuration file:**\n\n**Global settings (applies to all projects):**\n\n1. Create your configuration file anywhere on your system\n2. Go to **Settings → Tools → Compose Stability Analyzer**\n3. Scroll to \"Ignored Type Patterns\" and add your patterns directly\n4. Or reference a file path in the \"Stability configuration file\" field (global)\n\n**Per-project settings (recommended for teams):**\n\n1. Create a configuration file in your project (e.g., `config/stability-config.txt`)\n2. Go to **Settings → Tools → Compose Stability Analyzer → Project Configuration**\n3. Set the path to your configuration file\n4. Commit the file to version control so your team shares the same configuration\n\n**Per-project settings take precedence** over global settings. This means you can have:\n- Global settings for your personal preferences\n- Project-specific settings that your entire team uses\n\n## Gradle Plugin for Tracking Runtime Recomposition and Stability Validation\n\nYou can track the recomposition for specific composable functions with the `@TraceRecomposition` annotation at runtime (KMP supports). You don't need to write any logging code yourself, just add the annotation, run your app, and watch detailed recomposition logs appear in Logcat. This compiler plugin supports Kotlin Multiplatform.\n\n![preview](art/preview6.png)\n\nThis is incredibly useful for:\n- **Debugging performance issues**: Find out which composables recompose too often, and why it was happen.\n- **Monitor stability performance**: Set a threshold (`@TraceRecomposition(threshold = 15)`) and send a Firebase event or any custom analytics event to your cloud service to track which composable functions are experiencing excessive recompositions and examine the problems.\n- **Understanding Compose behavior**: Learn how state changes trigger recompositions.\n- **Validating optimizations**: Confirm your stability fixes actually work.\n\n\u003e **Note**: This library is completely independent of the Compose Stability Analyzer IntelliJ plugin and is entirely optional. You can choose to integrate it only if you find it suitable for your project.\n\n### Including in your project \n\n[![Maven Central](https://img.shields.io/maven-central/v/com.github.skydoves/compose-stability-runtime.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.github.skydoves%22%20AND%20a:%compose-stability-runtime%22)\n\nFirst, add the plugin to the `[plugins]` section of your `libs.versions.toml` file:\n\n```toml\nstability-analyzer = { id = \"com.github.skydoves.compose.stability.analyzer\", version = \"0.7.0\" }\n```\n\nThen, apply it to your root `build.gradle.kts` with `apply false`:\n\n```kotlin\nalias(libs.plugins.stability.analyzer) apply false\n```\n\nFinally, apply the plugin to your app or shared module's `build.gradle.kts`:\n\n```kotlin\nalias(libs.plugins.stability.analyzer)\n```\nSync your project to complete the setup.\n\n### Kotlin Version Mapping\n\nIt’s **strongly recommended to use the exact same Kotlin version** as this library. Using a different Kotlin version may lead to compilation errors during the build process.\n\n| Stability Analyzer | Kotlin |\n|--------------------|-------------|\n| 0.6.5+             | 2.3.0 |\n| 0.4.0~0.6.4        | 2.2.21 |\n\n### TraceRecomposition Annotation\n\n`@TraceRecomposition` lets you trace the behavior of any composable function. By annotating a composable with `@TraceRecomposition`, you can log parameter changes whenever that function undergoes recomposition.\n\n```kotlin\n@TraceRecomposition\n@Composable\nfun UserProfile(user: User) {\n    Column {\n        Text(\"Name: ${user.name}\")\n        Text(\"Age: ${user.age}\")\n    }\n}\n```\n\nThat's it. When this composable recomposes, you'll see logs like:\n\n```\nD/Recomposition: [Recomposition #1] UserProfile\nD/Recomposition:   └─ user: User stable (User@abc123)\nD/Recomposition: [Recomposition #2] UserProfile\nD/Recomposition:   └─ user: User changed (User@abc123 → User@def456)\n```\n\n### Annotation Parameters\n\nThe `@TraceRecomposition` annotation accepts two optional parameters to help you organize and filter logs:\n\n#### The `threshold` Parameter\n\nYou can configure the `threshold` parameter in the `@TraceRecomposition` annotation to log only when the recomposition count exceeds the specified threshold. This helps reduce noise from composables that frequently recompose. Additionally, you can use the recomposition callback for performance monitoring by sending custom events to Firebase or any other analytics platform.\n\n```kotlin\n@TraceRecomposition(threshold = 3)\n@Composable\nfun FrequentlyRecomposingScreen() {\n    // Will only start logging after the 3rd recomposition\n}\n```\n\n**Why thresholds matter**\n\nMany composables recompose 1-2 times during initial setup. These are expected and not performance issues. By using `threshold = 3` or a specific number, you filter out the noise and focus on actual problems, composables that keep recomposing during user interaction.\n\nThe real example might be like so:\n\n```kotlin\nComposeStabilityAnalyzer.setLogger(object : RecompositionLogger {\n  override fun log(event: RecompositionEvent) {\n    // Track excessive recompositions\n    if (event.recompositionCount \u003e= 10) {\n      // Example: Send to Firebase Analytics\n      FirebaseAnalytics.getInstance(this).logEvent(\"excessive_recomposition\") {\n        param(\"tag\", event.tag)\n        param(\"composable\", event.composableName)\n        param(\"count\", event.recompositionCount)\n        param(\"unstable_params\", event.unstableParameters.joinToString())\n      }\n    }\n  }\n})\n```\n\n#### The `tag` parameter: filter your logs\n\nUse tags to categorize and filter your logs. Tags are especially useful when tracking multiple composables across different screens or features.\n\n```kotlin\n@TraceRecomposition(tag = \"user-profile\")\n@Composable\nfun UserProfile(user: User) {\n    // Your composable code\n}\n```\n\nNow logs include the tag:\n\n```\nD/Recomposition: [Recomposition #1] UserProfile (tag: user-profile)\nD/Recomposition:   └─ user: User stable (User@abc123)\n```\n\nThis 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:\n\n```kotlinp\nval tagsToLog = setOf(\"user-profile\", \"checkout\", \"performance\")\n\nComposeStabilityAnalyzer.setLogger(object : RecompositionLogger {\n  override fun log(event: RecompositionEvent) {\n    if (!BuildConfig.DEBUG) {\n        if (event.tag in tagsToLog || event.tag.isEmpty()) {\n        // Example: Send to Firebase Analytics only log events with specific tags\n        FirebaseAnalytics.getInstance(this).logEvent(\"excessive_recomposition\") {\n          param(\"tag\", event.tag)\n          param(\"composable\", event.composableName)\n          param(\"count\", event.recompositionCount)\n          param(\"unstable_params\", event.unstableParameters.joinToString())\n        }\n      }\n    } else {\n        // Log everything on the debug mode\n        Log.d(..)\n    }\n  }\n})\n```\n\n**Filtering Logcat with tags**\n\nOnce you have tags, you can filter Logcat to see only specific composables:\n\n- See all recompositions: Filter by `Recomposition`\n- See a tagged recompositions: Filter by `tag: \u003ctag name\u003e`\n- See specific composable: Filter by `UserProfile` and `Recomposition`\n\n### Configure Custom Logger \u0026 Enable Logging\n\nYou need to enable or disable the logging system in your app. Add this to your `Application` class:\n\n```kotlin\nclass MyApp : Application() {\n    override fun onCreate() {\n        super.onCreate()\n\n        // Enable recomposition tracking ONLY in debug builds\n        ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)\n    }\n}\n```\n\n**Important Note**\n\n- Always wrap with `BuildConfig.DEBUG` to avoid performance overhead in production or filter them clearly on the custom logger.\n- If you don't enable `ComposeStabilityAnalyzer`, no logs will appear even with `@TraceRecomposition`.\n- This logging has minimal performance impact in debug builds but should still be disabled in release builds for any security reasons of your app.\n\nAlso, you can completely redefine the logging behaviors by setting your custom logger like the example below:\n\n```kotlin\nComposeStabilityAnalyzer.setLogger(object : RecompositionLogger {\n  override fun log(event: RecompositionEvent) {\n    val message = buildString {\n      append(\"🔄 Recomposition #${event.recompositionCount}\")\n      append(\" - ${event.composableName}\")\n      if (event.tag.isNotEmpty()) {\n        append(\" [${event.tag}]\")\n      }\n      appendLine()\n\n      event.parameterChanges.forEach { change -\u003e\n        append(\"   • ${change.name}: ${change.type}\")\n        when {\n          change.changed -\u003e append(\" ➡️ CHANGED\")\n          change.stable -\u003e append(\" ✅ STABLE\")\n          else -\u003e append(\" ⚠️ UNSTABLE\")\n        }\n        appendLine()\n      }\n\n      if (event.unstableParameters.isNotEmpty()) {\n        append(\"   ⚠️ Unstable: ${event.unstableParameters.joinToString()}\")\n      }\n    }\n\n    Log.d(\"CustomRecomposition\", message)\n  }\n})\n```\n\n### Reading the Logs\n\nLet's understand what each log tells you:\n\n#### First Recomposition\n\n```\nD/Recomposition: [Recomposition #1] UserProfile\nD/Recomposition:   └─ user: User stable (User@abc123)\n```\n\n**What this means:**\n- `[Recomposition #1]` - This is the first time this composable instance is recomposing\n- `UserProfile` - The name of the composable function\n- `user: User` - Parameter name and type\n- `stable` - This parameter is stable\n- `(User@abc123)` - The current value's identity (hashcode)\n\nThis log confirms the composable is working correctly. The parameter is stable and the first recomposition is expected.\n\n#### Parameter Changed\n\n```\nD/Recomposition: [Recomposition #2] UserProfile\nD/Recomposition:   └─ user: User changed (User@abc123 → User@def456)\n```\n\n**What this means:**\n- `[Recomposition #2]` - Second recomposition\n- `changed` - The parameter value changed (this is **why** it recomposed)\n- `(User@abc123 → User@def456)` - Shows old value → new value\n\nThis is normal behavior. The parameter changed, so the composable recomposed to show the new data. This is exactly what Compose should do.\n\n#### Unstable Parameter\n\n```\nD/Recomposition: [Recomposition #1] UserCard (tag: user-card)\nD/Recomposition:   ├─ user: MutableUser unstable (MutableUser@xyz789)\nD/Recomposition:   └─ Unstable parameters: [user]\n```\n\n#### Multiple Parameters (Mixed Stability)\n\n```\nD/Recomposition: [Recomposition #5] ProductList (tag: products)\nD/Recomposition:   ├─ title: String stable (Products)\nD/Recomposition:   ├─ count: Int changed (4 → 5)\nD/Recomposition:   ├─ items: List\u003cProduct\u003e unstable (List@abc)\nD/Recomposition:   └─ Unstable parameters: [items]\n```\n\n**What this means**\n\n- `title: String stable` - Not causing recomposition\n- `count: Int changed (4 → 5)` - **This is why it recomposed** (count changed from 4 to 5).\n- `items: List\u003cProduct\u003e unstable` - This list is unstable, causing unnecessary recompositions.\n- `Unstable parameters: [items]` - Summary.\n\n### Real-World Example\n\nLet's walk through a complete debugging session using `@TraceRecomposition`:\n\n**Problem:** Your product list screen feels laggy. You suspect excessive recompositions.\n\n**Step 1: Add tracking**\n\n```kotlin\n@TraceRecomposition(tag = \"product-card\", threshold = 3)\n@Composable\nfun ProductCard(\n    product: Product,\n    onClick: () -\u003e Unit\n) {\n    Card(onClick = onClick) {\n        Text(product.name)\n        Text(\"$${product.price}\")\n    }\n}\n```\n\n**Step 2: Run your app and check Logcat**\n\n```\nD/Recomposition: [Recomposition #3] ProductCard (tag: product-card)\nD/Recomposition:   ├─ product: Product unstable (Product@abc)\nD/Recomposition:   ├─ onClick: () -\u003e Unit stable (Function@xyz)\nD/Recomposition:   └─ Unstable parameters: [product]\n\nD/Recomposition: [Recomposition #4] ProductCard (tag: product-card)\nD/Recomposition:   ├─ product: Product unstable (Product@abc)\nD/Recomposition:   ├─ onClick: () -\u003e Unit stable (Function@xyz)\nD/Recomposition:   └─ Unstable parameters: [product]\n\n... (logs continue every scroll)\n```\n\n**Step 3: Analyze**\n\nThe logs reveal:\n- `onClick` is stable.\n- `product` is unstable.\n- `ProductCard` is recomposing 3+ times (that's why we see logs).\n\n**Step 4: Check your `Product` class**\n\n```kotlin\n// Current implementation (UNSTABLE)\ndata class Product(\n    var name: String,     // ← var = mutable = unstable!\n    var price: Double     // ← var = mutable = unstable!\n)\n```\n\n**Step 5: Fix it**\n\n```kotlin\n// Fixed implementation (STABLE)\ndata class Product(\n    val name: String,     // ← val = read-only = stable\n    val price: Double     // ← val = read-only = stable\n)\n```\n\n**Step 6: Verify the fix**\n\nRun the app again and check Logcat:\n\n```\nD/Recomposition: [Recomposition #3] ProductCard (tag: product-card)\nD/Recomposition:   ├─ product: Product stable (Product@abc)\nD/Recomposition:   └─ onClick: () -\u003e Unit stable (Function@xyz)\n\n(No more excessive recompositions!)\n```\n\n### Best Practices\n\n**1. Don't track everything**: Be selective about which composables you track. Focus on:\n- Composables you suspect have performance issues.\n- List items (they recompose frequently).\n- Complex screens with many parameters.\n\n**2. Use meaningful tags**: Tags make filtering easier:\n\n```kotlin\n@TraceRecomposition(tag = \"auth-flow\")       // Track entire feature\n@TraceRecomposition(tag = \"login-button\")    // Track specific component\n```\n\n**3. Set appropriate thresholds**: Reduce noise with thresholds:\n\n```kotlin\n@TraceRecomposition(threshold = 3)  // Most common—skip initial setup\n@TraceRecomposition(threshold = 10) // For very active composables\n```\n\n## Stability Validation\n\nImagine this scenario: You've spent weeks optimizing your app's Compose performance. All your composables are stable, skippable, and lightning-fast. Then someone on your team innocently changes a `val` to `var` in a data class, and suddenly dozens of composables become unstable. The performance regression slips through code review and makes it to production.\n\n**Stability Validation** prevents this nightmare. It's like git diff for composable stability, it tracks your composables' stability over time and automatically fails your CI build if stability regresses. You can check out the [quick integration codes](https://github.com/skydoves/landscapist/pull/767/files).\n\n### How It Works\n\nStability validation works through two Gradle tasks:\n\n1. **`stabilityDump`**: Creates a snapshot of all composables' stability.\n2. **`stabilityCheck`**: Compares current stability against the snapshot.\n\nThink of it like this:\n\n- `stabilityDump` = \"Save the current state\"\n- `stabilityCheck` = \"Has anything changed since last save?\"\n\n\u003e **Note**: Keep in mind that, all these Gradle tasks should be done **after compile your project**.\n\n### Android\n\nFor Android projects, variant-specific tasks will be created, such as `debugStabilityDump`.\nYou can use those to only compile one variant of your module.\n\n### Step 1: Create a Stability Baseline\n\nFirst, you need to generate a baseline—a snapshot of your current composables' stability.\n\nRun this command:\n\n```bash\n./gradlew :app:stabilityDump\n```\n\nThis creates a human-readable `.stability` file:\n\n```\napp/stability/app.stability\n```\n\n**What's in this file?**\n\nIt's a complete record of every composable in your module, showing:\n- Function signature (name, parameters, return type)\n- Whether it's skippable and restartable\n- Stability of each parameter\n\nThe `.stability` file will be looking like below:\n\n```\n@Composable\npublic fun com.example.UserCard(user: com.example.User): kotlin.Unit\n  skippable: true\n  restartable: true\n  params:\n    - user: STABLE (marked @Stable or @Immutable)\n\n@Composable\npublic fun com.example.ProductList(items: kotlin.collections.List\u003ccom.example.Product\u003e): kotlin.Unit\n  skippable: true\n  restartable: true\n  params:\n    - items: STABLE (immutable collection with stable elements)\n\n@Composable\npublic fun com.example.UnstableCard(user: com.example.MutableUser): kotlin.Unit\n  skippable: false\n  restartable: true\n  params:\n    - user: UNSTABLE (has mutable properties)\n```\n\nThis file is your **stability contract**. It says \"these are all my composables, and this is how stable they should be.\"\n\n**Commit this file to git:**\n\n```bash\ngit add app/stability/app.stability\ngit commit -m \"Add stability baseline for app module\"\ngit push\n```\n\nNow everyone on your team has the same baseline. Any changes to composable stability will be detected!\n\n### Step 2: Check for Stability Changes\n\nThe `stabilityCheck` task compares your current code against the baseline.\n\nRun this command:\n\n```bash\n./gradlew :app:stabilityCheck\n```\n\n**If nothing changed:**\n\n```\n✅ Stability check passed.\n```\n\nYour composables' stability matches the baseline. Everything is good!\n\n**If stability regressed:**\n\n```\n❌ Stability check failed!\n\nThe following composables have changed stability:\n\n~ com.example.UserCard(user): stability changed from STABLE to UNSTABLE\n\nIf these changes are intentional, run './gradlew stabilityDump' to update the stability file.\n```\n\nThe build **fails**, preventing the regression from being merged!\n\n**Types of changes detected**\n\nThe task detects four types of changes:\n\n| Symbol | Change Type | Example |\n|--------|-------------|---------|\n| `~` | Stability regressed | Parameter changed from STABLE to UNSTABLE |\n| `+` | New composable added | `+ com.example.NewScreen(title)` |\n| `-` | Composable removed | `- com.example.OldScreen(data)` |\n| `~` | Parameter count changed | Function signature changed |\n\n### Real-World Example\n\nLet's walk through a complete example:\n\n```kotlin\ndata class User(val name: String, val age: Int)\n\n@Composable\nfun UserCard(user: User) {\n    Text(\"${user.name}, ${user.age}\")\n}\n```\n\nGenerate baseline:\n\n```bash\n./gradlew :app:stabilityDump\ngit add app/stability/app.stability\ngit commit -m \"Add stability baseline\"\n```\n\nThe `.stability` file now contains:\n\n```\n@Composable\npublic fun com.example.UserCard(user: com.example.User): kotlin.Unit\n  skippable: true\n  params:\n    - user: STABLE\n```\n\n**If someone makes a change**\n\nA developer modifies the `User` class:\n\n```kotlin\ndata class User(var name: String, var age: Int)  // Changed val to var\n```\n\nThey create a pull request. Your CI runs:\n\n```bash\n./gradlew :app:compileDebugKotlin\n./gradlew :app:stabilityCheck\n```\n\n**CI output:**\n\n```\n❌ Stability check failed!\n\n~ com.example.UserCard(user): stability changed from STABLE to UNSTABLE\n\nIf these changes are intentional, run './gradlew stabilityDump' to update the stability file.\n```\n\nThe pull request cannot merge. The developer must either:\n\n1. **Fix the regression** - Change back to `val`.\n2. **Update the baseline** - If the change is intentional.\n\n**If the change is intentional**\n\n```bash\n./gradlew :app:stabilityDump  # Update baseline\ngit add app/stability/app.stability\ngit commit -m \"Accept UserCard stability regression (justified by...)\"\n```\n\nThis creates a **deliberate, documented decision** in git history, rather than an accidental regression.\n\n### CI/CD Integration\n\nAdd stability validation to your CI pipeline so it runs on every pull request:\n\n**GitHub Actions:**\n\n```yaml\nname: Android CI\n\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Set up JDK 17\n        uses: actions/setup-java@v3\n        with:\n          java-version: '17'\n          distribution: 'temurin'\n\n      - name: Build project\n        run: ./gradlew :app:compileDebugKotlin\n\n  stability_check:\n    name: Compose Stability Check\n    runs-on: ubuntu-latest\n    needs: build  \u003c\u003c\u003c\u003c\u003c This is important\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v5.0.0\n      - name: Set up JDK\n        uses: actions/setup-java@v5.0.0\n        with:\n          distribution: 'zulu'\n          java-version: 21\n      - name: compose stability check\n        run: ./gradlew stabilityCheck    \n```\n\nNow every pull request gets automatically checked for stability regressions!\n\n### Configuration\n\nYou can customize what gets tracked and where files are stored in your Gradle file:\n\n```kotlin\n// In your build.gradle.kts\ncomposeStabilityAnalyzer {\n\n    stabilityValidation {\n        enabled.set(true) // Enable or disable stability validation\n        outputDir.set(layout.projectDirectory.dir(\"stability\")) // set the output directory\n        includeTests.set(false) // Exclude test code from stability reports (default)\n\n        // Ignore specific packages or classes\n        ignoredPackages.set(listOf(\"com.example.internal\"))\n        ignoredClasses.set(listOf(\"PreviewComposables\"))\n\n        // Exclude specific sub-projects/modules (useful for multi-module projects)\n        ignoredProjects.set(listOf(\"benchmarks\", \"examples\", \"samples\"))\n\n        // Control build failure behavior on stability changes (default: true)\n        failOnStabilityChange.set(true)\n      \n        // Do not report any stable changes from the baseline (default: false)\n        ignoreNonRegressiveChanges.set(false)\n      \n        // Allow the check to run, even if the baseline does not exist (default: false)\n        allowMissingBaseline.set(false)\n    }\n}\n```\n\n#### `failOnStabilityChange` Option\n\nBy default, `stabilityCheck` will **fail the build** when stability changes are detected. This is ideal for CI/CD pipelines where you want to prevent stability regressions from being merged.\n\nHowever, in some scenarios you may want to **log warnings instead of failing**:\n\n```kotlin\ncomposeStabilityAnalyzer {\n    stabilityValidation {\n        // Log stability changes as warnings instead of failing the build\n        failOnStabilityChange.set(false)\n    }\n}\n```\n\n**When to use `failOnStabilityChange.set(false)`:**\n\n- **Initial adoption**: When first adding stability validation to an existing project, you may want to see all stability issues without blocking builds.\n- **Gradual migration**: Allow the team to fix stability issues incrementally while still tracking them.\n- **Development branches**: Use warnings during development, but enable strict mode for `main` branch.\n- **Monitoring only**: Track stability trends without enforcing them as build requirements.\n\n**Example: Different behavior per environment**\n\n```kotlin\ncomposeStabilityAnalyzer {\n    stabilityValidation {\n        // Fail on CI, warn locally\n        failOnStabilityChange.set(System.getenv(\"CI\") == \"true\")\n    }\n}\n```\n\n**Why ignore packages/classes**\n\nIf you don't want to track:\n- Preview composables (only used in Android Studio previews)\n- Test composables (only used in UI tests)\n- Debug screens (only in debug builds)\n\nThese composables aren't in production, so their stability doesn't matter.\n\n### Excluding Composables from Reports\n\nSometimes you have composables that shouldn't be included in stability validation:\n\n- **Preview composables**: Only used for Android Studio previews.\n- **Debug/test composables**: Only in debug builds.\n- **Experimental composables**: Still under development.\n\nUse the `@IgnoreStabilityReport` annotation to exclude them:\n\n```kotlin\n@IgnoreStabilityReport\n@Preview\n@Composable\nfun UserCardPreview() {\n    UserCard(user = User(\"John\", 30))\n}\n```\n\nThis composable will be **excluded** from:\n\n- `.stability` files generated by `stabilityDump`\n- Stability validation checks by `stabilityCheck`\n\n### Multi-Module Projects\n\nFor projects with multiple modules, each module gets its own `.stability` file:\n\n```\nproject/\n├── app/\n│   └── stability/\n│       └── app.stability\n├── feature-auth/\n│   └── stability/\n│       └── feature-auth.stability\n└── feature-profile/\n    └── stability/\n        └── feature-profile.stability\n```\n\nRun `stabilityCheck` for all modules at once:\n\n```bash\n./gradlew stabilityCheck\n```\n\nOr check specific modules:\n\n```bash\n./gradlew :app:stabilityCheck\n./gradlew :feature-auth:stabilityCheck\n```\n\n## Find this library useful? :heart:\n\nSupport it by joining __[stargazers](https://github.com/skydoves/compose-stability-analyzer/stargazers)__ for this repository. :star: \u003cbr\u003e\nAlso __[follow](https://github.com/skydoves)__ me for my next creations! 🤩\n\n# License\n\n```xml\nDesigned and developed by 2025 skydoves (Jaewoong Eum)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","funding_links":["https://github.com/sponsors/skydoves","https://www.android.skydoves.me/","https://kotlin.skydoves.me/","https://github.com/doveletter"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskydoves%2Fcompose-stability-analyzer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskydoves%2Fcompose-stability-analyzer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskydoves%2Fcompose-stability-analyzer/lists"}