---
title: "Observing Compose Runtime with CompositionObserver"
description: "Track state reads and recomposition scope lifecycles with CompositionObserver, and understand the observation fixes in Compose Runtime 1.13.0-beta01."
pubDatetime: 2026-10-29
author: "Arda Kazancı"
canonicalUrl: "https://ardakazanci.com/posts/observing-compose-runtime-with-compositionobserver/"
tags: ["kotlin", "android", "compose", "debugging"]
---
Compose Runtime associates state reads with recomposition scopes. `CompositionObserver` is an experimental API for observing those relationships. If you're building a recomposition analysis tool, it lets you track which state a scope reads, when it becomes invalid, and when it leaves the composition. [API reference](https://developer.android.com/reference/kotlin/androidx/compose/runtime/tooling/CompositionObserver).

Attach an observer to an existing composition with `composition.setObserver(observer)`:

```kotlin title="AttachObserver.kt"
import androidx.compose.runtime.Composition
import androidx.compose.runtime.ExperimentalComposeRuntimeApi
import androidx.compose.runtime.tooling.CompositionObserver
import androidx.compose.runtime.tooling.CompositionObserverHandle
import androidx.compose.runtime.tooling.setObserver

@OptIn(ExperimentalComposeRuntimeApi::class)
fun attachObserver(
    composition: Composition,
    observer: CompositionObserver
): CompositionObserverHandle? = composition.setObserver(observer)

// Retain the returned handle and detach when finished:
// handle?.dispose()
```

The caller supplies an observer implementation and owns the returned handle. A `null` handle means the composition doesn't support observation. The API belongs to `androidx.compose.runtime:runtime` and requires `ExperimentalComposeRuntimeApi` opt-in. [Registration API](https://developer.android.com/reference/kotlin/androidx/compose/runtime/tooling/package-summary#extension-functions).

The observer receives composition start/end events and scope lifecycle callbacks. `onReadInScope(scope, value)` is particularly useful: it exposes the relationship between a scope and the object it reads. `value` is usually the snapshot state instance, rather than the value stored inside it.

For example, if a counter reads a `MutableState<Int>`, an analysis tool can associate that state object with the reading scope. When the scope is invalidated, `onScopeInvalidated` reports the associated object, or `null` for direct invalidation through `scope.invalidate()`. State-driven invalidations aren't necessarily reported at the moment of the write. [Callback contracts](https://developer.android.com/reference/kotlin/androidx/compose/runtime/tooling/CompositionObserver).

There are two limits to keep in mind. An invalidated scope isn't guaranteed to execute again: its parent might remove the branch containing it. Also, `onEndComposition` marks the end of composition processing; it doesn't mean a frame has been drawn. These callbacks shouldn't be treated as a direct measurement of frame time. [Scope lifecycle](https://developer.android.com/reference/kotlin/androidx/compose/runtime/tooling/CompositionObserver).

## What changed in 1.13.0-beta01?

The observer is now selected once at the beginning of a composition pass and kept for the entire pass. If you call `setObserver` halfway through, the replacement takes effect on the next pass. This keeps a pass's events associated with the same observer.

Movable content insertion now also reports `onBeginComposition` and `onEndComposition`. An observation tool needs those boundaries to interpret its event sequence consistently. Both fixes shipped in [Compose Runtime 1.13.0-beta01](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.13.0-beta01); the API itself predates that release.
