Introduce Snapshot.withoutReadObservation

Relnote: New function Snapshot.withoutReadObservation { ... } was added. It allows to run the passed lambda without subscribing to the changes of the state values read during this block. You could find it useful in use cases when you want to benefit from the snapshot based thread safe write/reads, but want to be able to read the value without causing unnecessary recomposition or remeasure.
Fixes: 214054486
Test: new tests in SnapshotStateObserverTestsJvm and RecomposerTests, rerun tests in ui module
Change-Id: I9f365d653483310cfda02cfa2c582fdcce8cfe33
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 1b8cb3e..6ac3a32 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -790,6 +790,7 @@
     method public androidx.compose.runtime.snapshots.MutableSnapshot takeMutableSnapshot(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver);
     method public androidx.compose.runtime.snapshots.Snapshot takeSnapshot(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver);
     method public inline <R> R! withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
+    method public inline <T> T! withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
   }
 
@@ -908,7 +909,7 @@
     method public <T> void observeReads(T scope, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onValueChangedForScope, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method public void start();
     method public void stop();
-    method public void withNoObservations(kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @Deprecated public void withNoObservations(kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public interface StateObject {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 0617f36..e7a5f70 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -857,6 +857,7 @@
     method public androidx.compose.runtime.snapshots.MutableSnapshot takeMutableSnapshot(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver);
     method public androidx.compose.runtime.snapshots.Snapshot takeSnapshot(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver);
     method public inline <R> R! withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
+    method public inline <T> T! withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
   }
 
@@ -975,7 +976,7 @@
     method public <T> void observeReads(T scope, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onValueChangedForScope, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method public void start();
     method public void stop();
-    method public void withNoObservations(kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @Deprecated public void withNoObservations(kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public interface StateObject {
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index fc57087..fb09c18 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -810,6 +810,7 @@
   }
 
   public static final class Snapshot.Companion {
+    method @kotlin.PublishedApi internal androidx.compose.runtime.snapshots.Snapshot createNonObservableSnapshot();
     method public androidx.compose.runtime.snapshots.Snapshot getCurrent();
     method public inline <T> T! global(kotlin.jvm.functions.Function0<? extends T> block);
     method public void notifyObjectsInitialized();
@@ -822,6 +823,7 @@
     method public androidx.compose.runtime.snapshots.MutableSnapshot takeMutableSnapshot(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver, optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? writeObserver);
     method public androidx.compose.runtime.snapshots.Snapshot takeSnapshot(optional kotlin.jvm.functions.Function1<java.lang.Object,kotlin.Unit>? readObserver);
     method public inline <R> R! withMutableSnapshot(kotlin.jvm.functions.Function0<? extends R> block);
+    method public inline <T> T! withoutReadObservation(kotlin.jvm.functions.Function0<? extends T> block);
     property public final androidx.compose.runtime.snapshots.Snapshot current;
   }
 
@@ -946,7 +948,7 @@
     method public <T> void observeReads(T scope, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onValueChangedForScope, kotlin.jvm.functions.Function0<kotlin.Unit> block);
     method public void start();
     method public void stop();
-    method public void withNoObservations(kotlin.jvm.functions.Function0<kotlin.Unit> block);
+    method @Deprecated public void withNoObservations(kotlin.jvm.functions.Function0<kotlin.Unit> block);
   }
 
   public interface StateObject {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 4d63c1a..f8ffc53 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -20,9 +20,13 @@
 
 import androidx.compose.runtime.AtomicReference
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisallowComposableCalls
 import androidx.compose.runtime.InternalComposeApi
 import androidx.compose.runtime.SnapshotThreadLocal
 import androidx.compose.runtime.synchronized
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 
 /**
  * A snapshot of the values return by mutable states and other state objects. All state object
@@ -409,9 +413,10 @@
                 val snapshot =
                     if (currentSnapshot == null || currentSnapshot is MutableSnapshot)
                         TransparentObserverMutableSnapshot(
-                            currentSnapshot as? MutableSnapshot,
-                            readObserver,
-                            writeObserver
+                            previousSnapshot = currentSnapshot as? MutableSnapshot,
+                            specifiedReadObserver = readObserver,
+                            specifiedWriteObserver = writeObserver,
+                            mergeParentObservers = true
                         )
                     else if (readObserver == null) return block()
                     else currentSnapshot.takeNestedSnapshot(readObserver)
@@ -423,6 +428,26 @@
             } else return block()
         }
 
+        @PublishedApi
+        internal fun createNonObservableSnapshot(): Snapshot =
+            createTransparentSnapshotWithNoParentReadObserver(
+                previousSnapshot = threadSnapshot.get()
+            )
+
+        /**
+         * Passed [block] will be run with all the currently set snapshot read observers disabled.
+         */
+        @OptIn(ExperimentalContracts::class)
+        inline fun <T> withoutReadObservation(block: @DisallowComposableCalls () -> T): T {
+            contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+            val snapshot = createNonObservableSnapshot()
+            try {
+                return snapshot.enter(block)
+            } finally {
+                snapshot.dispose()
+            }
+        }
+
         /**
          * Register an apply listener that is called back when snapshots are applied to the
          * global state.
@@ -1369,13 +1394,15 @@
 internal class TransparentObserverMutableSnapshot(
     private val previousSnapshot: MutableSnapshot?,
     internal val specifiedReadObserver: ((Any) -> Unit)?,
-    internal val specifiedWriteObserver: ((Any) -> Unit)?
+    internal val specifiedWriteObserver: ((Any) -> Unit)?,
+    private val mergeParentObservers: Boolean
 ) : MutableSnapshot(
     INVALID_SNAPSHOT,
     SnapshotIdSet.EMPTY,
     mergedReadObserver(
         specifiedReadObserver,
-        previousSnapshot?.readObserver ?: currentGlobalSnapshot.get().readObserver
+        previousSnapshot?.readObserver ?: currentGlobalSnapshot.get().readObserver,
+        mergeParentObservers
     ),
     mergedWriteObserver(
         specifiedWriteObserver,
@@ -1415,16 +1442,42 @@
     override fun recordModified(state: StateObject) =
         currentSnapshot.recordModified(state)
 
-    override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot =
-        currentSnapshot.takeNestedSnapshot(mergedReadObserver(readObserver, this.readObserver))
+    override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
+        val mergedReadObserver = mergedReadObserver(readObserver, this.readObserver)
+        return if (!mergeParentObservers) {
+            createTransparentSnapshotWithNoParentReadObserver(
+                previousSnapshot = currentSnapshot.takeNestedSnapshot(null),
+                readObserver = readObserver
+            )
+        } else {
+            currentSnapshot.takeNestedSnapshot(mergedReadObserver)
+        }
+    }
 
     override fun takeNestedMutableSnapshot(
         readObserver: ((Any) -> Unit)?,
         writeObserver: ((Any) -> Unit)?
-    ): MutableSnapshot = currentSnapshot.takeNestedMutableSnapshot(
-        mergedReadObserver(readObserver, this.readObserver),
-        mergedWriteObserver(writeObserver, this.writeObserver)
-    )
+    ): MutableSnapshot {
+        val mergedReadObserver = mergedReadObserver(readObserver, this.readObserver)
+        val mergedWriteObserver = mergedWriteObserver(writeObserver, this.writeObserver)
+        return if (!mergeParentObservers) {
+            val nestedSnapshot = currentSnapshot.takeNestedMutableSnapshot(
+                readObserver = null,
+                writeObserver = mergedWriteObserver
+            )
+            TransparentObserverMutableSnapshot(
+                previousSnapshot = nestedSnapshot,
+                specifiedReadObserver = mergedReadObserver,
+                specifiedWriteObserver = mergedWriteObserver,
+                mergeParentObservers = false
+            )
+        } else {
+            currentSnapshot.takeNestedMutableSnapshot(
+                mergedReadObserver,
+                mergedWriteObserver
+            )
+        }
+    }
 
     override fun notifyObjectsInitialized() = currentSnapshot.notifyObjectsInitialized()
 
@@ -1434,16 +1487,108 @@
     override fun nestedDeactivated(snapshot: Snapshot) = unsupported()
 }
 
+/**
+ * A pseudo snapshot that doesn't introduce isolation but does introduce observers.
+ */
+internal class TransparentObserverSnapshot(
+    private val previousSnapshot: Snapshot?,
+    specifiedReadObserver: ((Any) -> Unit)?,
+    private val mergeParentObservers: Boolean
+) : Snapshot(
+    INVALID_SNAPSHOT,
+    SnapshotIdSet.EMPTY,
+) {
+    override val readObserver: ((Any) -> Unit)? = mergedReadObserver(
+        specifiedReadObserver,
+        previousSnapshot?.readObserver ?: currentGlobalSnapshot.get().readObserver,
+        mergeParentObservers
+    )
+    override val writeObserver: ((Any) -> Unit)? = null
+
+    override val root: Snapshot = this
+
+    private val currentSnapshot: Snapshot
+        get() = previousSnapshot ?: currentGlobalSnapshot.get()
+
+    override fun dispose() {
+        // Explicitly don't call super.dispose()
+        disposed = true
+    }
+
+    override var id: Int
+        get() = currentSnapshot.id
+        @Suppress("UNUSED_PARAMETER")
+        set(value) { unsupported() }
+
+    override var invalid get() = currentSnapshot.invalid
+        @Suppress("UNUSED_PARAMETER")
+        set(value) = unsupported()
+
+    override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
+
+    override var modified: MutableSet<StateObject>?
+        get() = currentSnapshot.modified
+        @Suppress("UNUSED_PARAMETER")
+        set(value) = unsupported()
+
+    override val readOnly: Boolean
+        get() = currentSnapshot.readOnly
+
+    override fun recordModified(state: StateObject) =
+        currentSnapshot.recordModified(state)
+
+    override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
+        val mergedReadObserver = mergedReadObserver(readObserver, this.readObserver)
+        return if (!mergeParentObservers) {
+            createTransparentSnapshotWithNoParentReadObserver(
+                previousSnapshot = currentSnapshot.takeNestedSnapshot(null),
+                readObserver = readObserver
+            )
+        } else {
+            currentSnapshot.takeNestedSnapshot(mergedReadObserver)
+        }
+    }
+
+    override fun notifyObjectsInitialized() = currentSnapshot.notifyObjectsInitialized()
+
+    /** Should never be called. */
+    override fun nestedActivated(snapshot: Snapshot) = unsupported()
+
+    override fun nestedDeactivated(snapshot: Snapshot) = unsupported()
+}
+
+private fun createTransparentSnapshotWithNoParentReadObserver(
+    previousSnapshot: Snapshot?,
+    readObserver: ((Any) -> Unit)? = null,
+): Snapshot = if (previousSnapshot is MutableSnapshot || previousSnapshot == null) {
+    TransparentObserverMutableSnapshot(
+        previousSnapshot = previousSnapshot as? MutableSnapshot,
+        specifiedReadObserver = readObserver,
+        specifiedWriteObserver = null,
+        mergeParentObservers = false
+    )
+} else {
+    TransparentObserverSnapshot(
+        previousSnapshot = previousSnapshot,
+        specifiedReadObserver = readObserver,
+        mergeParentObservers = false
+    )
+}
+
 private fun mergedReadObserver(
     readObserver: ((Any) -> Unit)?,
-    parentObserver: ((Any) -> Unit)?
-): ((Any) -> Unit)? =
-    if (readObserver != null && parentObserver != null && readObserver != parentObserver) {
+    parentObserver: ((Any) -> Unit)?,
+    mergeReadObserver: Boolean = true
+): ((Any) -> Unit)? {
+    @Suppress("NAME_SHADOWING")
+    val parentObserver = if (mergeReadObserver) parentObserver else null
+    return if (readObserver != null && parentObserver != null && readObserver != parentObserver) {
         { state: Any ->
             readObserver(state)
             parentObserver(state)
         }
     } else readObserver ?: parentObserver
+}
 
 private fun mergedWriteObserver(
     writeObserver: ((Any) -> Unit)?,
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index 2cff64d..b6880e8 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -68,12 +68,6 @@
     private var applyUnsubscribe: ObserverHandle? = null
 
     /**
-     * `true` when an [observeReads] is in progress and [readObserver] is active and `false` when
-     * [readObserver] is no longer observing changes.
-     */
-    private var isObserving = false
-
-    /**
      * `true` when [withNoObservations] is called and read observations should no
      * longer be considered invalidations for the `onCommit` callback.
      */
@@ -115,16 +109,7 @@
             }
         }
 
-        if (!isObserving) {
-            isObserving = true
-            try {
-                Snapshot.observe(readObserver, null, block)
-            } finally {
-                isObserving = false
-            }
-        } else {
-            block()
-        }
+        Snapshot.observe(readObserver, null, block)
 
         currentMap = oldMap
         applyMap.currentScope = oldScope
@@ -135,6 +120,13 @@
      * Stops observing state object reads while executing [block]. State object reads may be
      * restarted by calling [observeReads] inside [block].
      */
+    @Deprecated(
+        "Replace with Snapshot.withoutReadObservation()",
+        ReplaceWith(
+            "Snapshot.withoutReadObservation(block)",
+            "androidx.compose.runtime.snapshots.Snapshot"
+        )
+    )
     fun withNoObservations(block: () -> Unit) {
         val oldPaused = isPaused
         isPaused = true
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
index 90cc577..0a2bbe1 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/RecomposerTests.kt
@@ -280,6 +280,30 @@
             "child did not inherit parent recomposeCoroutineContext"
         )
     }
+
+    @Test
+    fun readDuringWithoutReadObservationDoesntCauseRecomposition() = compositionTest {
+        var someState by mutableStateOf(0)
+        var recompostions = 0
+
+        @Composable
+        fun use(@Suppress("UNUSED_PARAMETER") i: Int) {
+        }
+
+        compose {
+            recompostions++
+            use(
+                Snapshot.withoutReadObservation { someState }
+            )
+        }
+
+        assertEquals(1, recompostions)
+
+        someState++
+        advance()
+
+        assertEquals(1, recompostions)
+    }
 }
 
 class UnitApplier : Applier<Unit> {
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt
index cc158b7..8dc0656 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt
@@ -17,9 +17,7 @@
 package androidx.compose.runtime.snapshots
 
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import kotlin.test.Test
 import kotlin.test.assertEquals
 
@@ -216,13 +214,14 @@
         }
     }
 
+    @Suppress("DEPRECATION")
     @Test
     fun pauseStopsObserving() {
         val data = "data"
         var changes = 0
 
         runSimpleTest { stateObserver, state ->
-            stateObserver.observeReads(data, { _ -> changes++ }) {
+            stateObserver.observeReads(data, { changes++ }) {
                 stateObserver.withNoObservations {
                     state.value
                 }
@@ -233,6 +232,40 @@
     }
 
     @Test
+    fun withoutReadObservationStopsObserving() {
+        val data = "data"
+        var changes = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads(data, { changes++ }) {
+                Snapshot.withoutReadObservation {
+                    state.value
+                }
+            }
+        }
+
+        assertEquals(0, changes)
+    }
+
+    @Test
+    fun changeAfterWithoutReadObservationIsObserving() {
+        val data = "data"
+        var changes = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads(data, { changes++ }) {
+                Snapshot.withoutReadObservation {
+                    state.value
+                }
+                state.value
+            }
+        }
+
+        assertEquals(1, changes)
+    }
+
+    @Suppress("DEPRECATION")
+    @Test
     fun nestedPauseStopsObserving() {
         val data = "data"
         var changes = 0
@@ -252,6 +285,25 @@
     }
 
     @Test
+    fun nestedWithoutReadObservation() {
+        val data = "data"
+        var changes = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads(data, { changes++ }) {
+                Snapshot.withoutReadObservation {
+                    Snapshot.withoutReadObservation {
+                        state.value
+                    }
+                    state.value
+                }
+            }
+        }
+
+        assertEquals(0, changes)
+    }
+
+    @Test
     fun simpleObserving() {
         val data = "data"
         var changes = 0
@@ -265,6 +317,7 @@
         assertEquals(1, changes)
     }
 
+    @Suppress("DEPRECATION")
     @Test
     fun observeWithinPause() {
         val data = "data"
@@ -284,6 +337,106 @@
         assertEquals(1, changes2)
     }
 
+    @Test
+    fun observeWithinWithoutReadObservation() {
+        val data = "data"
+        var changes1 = 0
+        var changes2 = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads(data, { changes1++ }) {
+                Snapshot.withoutReadObservation {
+                    stateObserver.observeReads(data, { changes2++ }) {
+                        state.value
+                    }
+                }
+            }
+        }
+        assertEquals(0, changes1)
+        assertEquals(1, changes2)
+    }
+
+    @Test
+    fun withoutReadsPausesNestedObservation() {
+        var changes1 = 0
+        var changes2 = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads("scope1", { changes1++ }) {
+                stateObserver.observeReads("scope2", { changes2++ }) {
+                    Snapshot.withoutReadObservation {
+                        state.value
+                    }
+                }
+            }
+        }
+        assertEquals(0, changes1)
+        assertEquals(0, changes2)
+    }
+
+    @Test
+    fun withoutReadsPausesNestedObservationWhenNewMutableSnapshotIsEnteredWithin() {
+        var changes1 = 0
+        var changes2 = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads("scope1", { changes1++ }) {
+                stateObserver.observeReads("scope2", { changes2++ }) {
+                    Snapshot.withoutReadObservation {
+                        val newSnapshot = Snapshot.takeMutableSnapshot()
+                        newSnapshot.enter {
+                            state.value
+                        }
+                        newSnapshot.apply().check()
+                        newSnapshot.dispose()
+                    }
+                }
+            }
+        }
+        assertEquals(0, changes1)
+        assertEquals(0, changes2)
+    }
+
+    @Test
+    fun withoutReadsPausesNestedObservationWhenNewSnapshotIsEnteredWithin() {
+        var changes1 = 0
+        var changes2 = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads("scope1", { changes1++ }) {
+                stateObserver.observeReads("scope2", { changes2++ }) {
+                    Snapshot.withoutReadObservation {
+                        val newSnapshot = Snapshot.takeSnapshot()
+                        newSnapshot.enter {
+                            state.value
+                        }
+                        newSnapshot.dispose()
+                    }
+                }
+            }
+        }
+        assertEquals(0, changes1)
+        assertEquals(0, changes2)
+    }
+
+    @Test
+    fun withoutReadsInReadOnlySnapshot() {
+        var changes = 0
+
+        runSimpleTest { stateObserver, state ->
+            stateObserver.observeReads("scope", { changes++ }) {
+                val newSnapshot = Snapshot.takeSnapshot()
+                newSnapshot.enter {
+                    Snapshot.withoutReadObservation {
+                        state.value
+                    }
+                }
+                newSnapshot.dispose()
+            }
+        }
+        assertEquals(0, changes)
+    }
+
     private fun runSimpleTest(
         block: (modelObserver: SnapshotStateObserver, data: MutableState<Int>) -> Unit
     ) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
index ae5fb6d..f665e62 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/SubcomposeLayout.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCompositionContext
 import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.SubcomposeLayoutState.PrecomposedSlotHandle
 import androidx.compose.ui.UiComposable
@@ -376,7 +377,7 @@
     }
 
     private fun subcompose(node: LayoutNode, nodeState: NodeState) {
-        node.withNoSnapshotReadObservation {
+        Snapshot.withoutReadObservation {
             ignoreRemeasureRequests {
                 val content = nodeState.content
                 nodeState.composition = subcomposeInto(
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index c87227e..21abe7c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -1149,14 +1149,6 @@
         }
     }
 
-    /**
-     * Execute your code within the [block] if you want some code to not be observed for the
-     * model reads even if you are currently inside some observed scope like measuring.
-     */
-    internal fun withNoSnapshotReadObservation(block: () -> Unit) {
-        requireOwner().snapshotObserver.withNoSnapshotReadObservation(block)
-    }
-
     internal fun dispatchOnPositionedCallbacks() {
         if (layoutState != Idle || layoutPending || measurePending) {
             return // it hasn't yet been properly positioned, so don't make a call
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index f9a387f..9ca5d20 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.ui.node
 
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.MutableRect
@@ -239,14 +240,11 @@
 
     fun onMeasured() {
         if (entities.has(EntityList.RemeasureEntityType)) {
-            val invokeRemeasureCallbacks = {
+            Snapshot.withoutReadObservation {
                 entities.forEach(EntityList.RemeasureEntityType) {
                     it.modifier.onRemeasured(measuredSize)
                 }
             }
-            layoutNode.owner?.snapshotObserver?.withNoSnapshotReadObservation(
-                invokeRemeasureCallbacks
-            ) ?: invokeRemeasureCallbacks()
         }
     }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
index a16df8e..532c7e3 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OwnerSnapshotObserver.kt
@@ -46,16 +46,6 @@
     }
 
     /**
-     * Observing the snapshot reads are temporary disabled during the [block] execution.
-     * For example if we are currently within the measure stage and we want some code block to
-     * be skipped from the observing we disable if before calling the block, execute block and
-     * then enable it again.
-     */
-    internal fun withNoSnapshotReadObservation(block: () -> Unit) {
-        observer.withNoObservations(block)
-    }
-
-    /**
      * Observe snapshot reads during layout of [node], executed in [block].
      */
     internal fun observeLayoutSnapshotReads(node: LayoutNode, block: () -> Unit) {