Merge "Propogate invalidations through CompositionReferences properly" into androidx-master-dev
diff --git a/compose/runtime/src/main/java/androidx/compose/Compose.kt b/compose/runtime/src/main/java/androidx/compose/Compose.kt
index 00e85d0..44f1981 100644
--- a/compose/runtime/src/main/java/androidx/compose/Compose.kt
+++ b/compose/runtime/src/main/java/androidx/compose/Compose.kt
@@ -33,10 +33,10 @@
 object Compose {
 
     private class Root : Component() {
-        @Suppress("DEPRECATION")
-        fun update() = recomposeSync()
+        fun update() = composer.compose()
 
         lateinit var composable: @Composable() () -> Unit
+        lateinit var composer: CompositionContext
         @Suppress("PLUGIN_ERROR")
         override fun compose() {
             val cc = currentComposerNonNull
@@ -127,15 +127,18 @@
             root = Root()
             root.composable = composable
             setRoot(container, root)
-            CompositionContext.prepare(
+            val cc = CompositionContext.prepare(
                 container.context,
                 container,
                 root,
                 parent
-            ).compose()
+            )
+            root.composer = cc
+            root.update()
+            return cc
         } else {
             root.composable = composable
-            root.recomposeCallback?.invoke(true)
+            root.update()
         }
         return null
     }
@@ -193,10 +196,12 @@
             root = Root()
             root.composable = composable
             setRoot(container, root)
-            CompositionContext.prepare(context, container, root, parent).compose()
+            val cc = CompositionContext.prepare(context, container, root, parent)
+            root.composer = cc
+            root.update()
         } else {
             root.composable = composable
-            root.recomposeCallback?.invoke(true)
+            root.update()
         }
     }
 
diff --git a/compose/runtime/src/main/java/androidx/compose/Composer.kt b/compose/runtime/src/main/java/androidx/compose/Composer.kt
index d5c6013..0811e53 100644
--- a/compose/runtime/src/main/java/androidx/compose/Composer.kt
+++ b/compose/runtime/src/main/java/androidx/compose/Composer.kt
@@ -161,7 +161,7 @@
     var location: Int
 )
 
-internal class RecomposeScope(val compose: (invalidate: (sync: Boolean) -> Unit) -> Unit) {
+internal class RecomposeScope(var compose: (invalidate: (sync: Boolean) -> Unit) -> Unit) {
     var anchor: Anchor? = null
     var invalidate: ((sync: Boolean) -> Unit)? = null
     val valid: Boolean get() = anchor?.valid ?: false
@@ -196,6 +196,7 @@
     private val invalidateStack = Stack<RecomposeScope>()
 
     internal var parentReference: CompositionReference? = null
+    internal var isComposing = false
 
     private val changesAppliedObservers = mutableListOf<() -> Unit>()
 
@@ -596,7 +597,7 @@
             invalidateStack.let { if (it.isNotEmpty()) it.peek() else null }
 
     private fun start(key: Any, action: SlotAction) {
-        assert(childrenAllowed) { "A call to creadNode(), emitNode() or useNode() expected" }
+        assert(childrenAllowed) { "A call to createNode(), emitNode() or useNode() expected" }
         if (pending == null) {
             val slotKey = slots.next()
             if (slotKey == key) {
@@ -905,6 +906,8 @@
     }
 
     private fun recomposeComponentRange(start: Int, end: Int) {
+        val wasComposing = isComposing
+        isComposing = true
         var recomposed = false
 
         var firstInRange = invalidations.firstInRange(start, end)
@@ -937,16 +940,26 @@
             recordSkip(START_GROUP)
             slots.skipGroup()
         }
+        isComposing = wasComposing
     }
 
     private fun invalidate(scope: RecomposeScope, sync: Boolean) {
         val location = scope.anchor?.location(slotTable) ?: return
         assert(location >= 0) { "Invalid anchor" }
         invalidations.insertIfMissing(location, scope)
-        if (sync) {
-            recomposer.recomposeSync(this)
+        if (isComposing && location > slots.current) {
+            // if we are invalidating a scope that is going to be traversed during this
+            // composition, we don't want to schedule a recomposition
+            return
+        }
+        if (parentReference != null) {
+            parentReference?.invalidate(sync)
         } else {
-            recomposer.scheduleRecompose(this)
+            if (sync) {
+                recomposer.recomposeSync(this)
+            } else {
+                recomposer.scheduleRecompose(this)
+            }
         }
     }
 
@@ -985,6 +998,7 @@
                 slots.startGroup()
                 @Suppress("UNCHECKED_CAST")
                 val scope = slots.next() as RecomposeScope
+                scope.compose = compose
                 invalidateStack.push(scope)
                 recordStart(START_GROUP)
                 skipValue()
@@ -1211,7 +1225,7 @@
 
         override fun <T> invalidateConsumers(key: Ambient<T>) {
             // need to mark the recompose scope that created the reference as invalid
-            invalidate()
+            invalidate(false)
 
             // loop through every child composer
             for (composer in composers) {
@@ -1227,11 +1241,11 @@
             composers.add(composer)
         }
 
-        override fun invalidate() {
+        override fun invalidate(sync: Boolean) {
             // continue invalidating up the spine of AmbientReferences
-            parentReference?.invalidate()
+            parentReference?.invalidate(sync)
 
-            scope.invalidate?.invoke(false)
+            invalidate(scope, sync)
         }
 
         override fun <T> getAmbient(key: Ambient<T>): T {
diff --git a/compose/runtime/src/main/java/androidx/compose/CompositionReference.kt b/compose/runtime/src/main/java/androidx/compose/CompositionReference.kt
index 8ba51d8..e6335a2 100644
--- a/compose/runtime/src/main/java/androidx/compose/CompositionReference.kt
+++ b/compose/runtime/src/main/java/androidx/compose/CompositionReference.kt
@@ -28,7 +28,7 @@
  */
 interface CompositionReference {
     fun <T> getAmbient(key: Ambient<T>): T
-    fun invalidate()
+    fun invalidate(sync: Boolean)
     fun <T> invalidateConsumers(key: Ambient<T>)
     fun <N> registerComposer(composer: Composer<N>)
 }
\ No newline at end of file
diff --git a/compose/runtime/src/main/java/androidx/compose/Key.kt b/compose/runtime/src/main/java/androidx/compose/Key.kt
index 4df25d3..5557f00 100644
--- a/compose/runtime/src/main/java/androidx/compose/Key.kt
+++ b/compose/runtime/src/main/java/androidx/compose/Key.kt
@@ -46,7 +46,7 @@
  *         Key(parent.id) {
  *             User(user=child)
  *             User(user=parent)
-*          }
+ *          }
  *     }
  *
  * This example assumes that `parent.id` is a unique key for each item in the collection,
diff --git a/compose/runtime/src/main/java/androidx/compose/Recomposer.kt b/compose/runtime/src/main/java/androidx/compose/Recomposer.kt
index 1fa38c1..094d849 100644
--- a/compose/runtime/src/main/java/androidx/compose/Recomposer.kt
+++ b/compose/runtime/src/main/java/androidx/compose/Recomposer.kt
@@ -49,10 +49,12 @@
     private val composers = mutableSetOf<Composer<*>>()
 
     private fun recompose(component: Component, composer: Composer<*>) {
-        val previousComposing = isComposing
         composer.runWithCurrent {
+            val previousComposing = isComposing
+            val composerWasComposing = composer.isComposing
             try {
                 isComposing = true
+                composer.isComposing = true
                 trace("Compose:recompose") {
                     composer.startRoot()
                     composer.startGroup(invocation)
@@ -64,6 +66,7 @@
                 FrameManager.nextFrame()
             } finally {
                 isComposing = previousComposing
+                composer.isComposing = composerWasComposing
             }
         }
     }
diff --git a/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt b/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
index c46ad69..710680e 100644
--- a/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
+++ b/ui/framework/src/androidTest/java/androidx/ui/core/test/AndroidLayoutDrawTest.kt
@@ -15,6 +15,7 @@
  */
 package androidx.ui.core.test
 
+import android.app.Activity
 import android.graphics.Bitmap
 import android.os.Build
 import android.os.Handler
@@ -44,10 +45,14 @@
 import androidx.ui.painting.Paint
 import androidx.compose.Children
 import androidx.compose.Composable
+import androidx.compose.Compose
 import androidx.compose.Model
 import androidx.compose.composer
 import androidx.compose.setContent
 import androidx.test.filters.SdkSuppress
+import androidx.ui.core.ContextAmbient
+import androidx.ui.core.Density
+import androidx.ui.core.DensityAmbient
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -391,6 +396,20 @@
         }
     }
 
+    // TODO(lmr): refactor to use the globally provided one when it lands
+    private fun Activity.compose(composable: @Composable() () -> Unit) {
+        val root = AndroidCraneView(this)
+
+        setContentView(root)
+        Compose.composeInto(root.root, context = this) {
+            ContextAmbient.Provider(value = this) {
+                DensityAmbient.Provider(value = Density(this)) {
+                    composable()
+                }
+            }
+        }
+    }
+
     // When a child's measure() is done within the layout, it should not affect the parent's
     // size. The parent's layout shouldn't be called when the child's size changes
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@@ -404,38 +423,36 @@
 
         val layoutLatch = CountDownLatch(2)
         runOnUiThread {
-            activity.setContent {
-                CraneWrapper {
-                    Draw { canvas, parentSize ->
-                        val paint = Paint()
-                        paint.color = model.outerColor
-                        canvas.drawRect(parentSize.toRect(), paint)
-                    }
-                    Layout(children = {
-                        AtLeastSize(size = model.size) {
-                            Draw { canvas, parentSize ->
-                                drawLatch.countDown()
-                                val paint = Paint()
-                                paint.color = model.innerColor
-                                canvas.drawRect(parentSize.toRect(), paint)
-                            }
-                        }
-                    }, layoutBlock = { measurables, constraints ->
-                        measureCalls++
-                        layout(30.ipx, 30.ipx) {
-                            layoutCalls++
-                            layoutLatch.countDown()
-                            val placeable = measurables[0].measure(constraints)
-                            placeable.place(
-                                (30.ipx - placeable.width) / 2,
-                                (30.ipx - placeable.height) / 2
-                            )
-                        }
-                    })
+            activity.compose {
+                Draw { canvas, parentSize ->
+                    val paint = Paint()
+                    paint.color = model.outerColor
+                    canvas.drawRect(parentSize.toRect(), paint)
                 }
+                Layout(children = {
+                    AtLeastSize(size = model.size) {
+                        Draw { canvas, parentSize ->
+                            drawLatch.countDown()
+                            val paint = Paint()
+                            paint.color = model.innerColor
+                            canvas.drawRect(parentSize.toRect(), paint)
+                        }
+                    }
+                }, layoutBlock = { measurables, constraints ->
+                    measureCalls++
+                    layout(30.ipx, 30.ipx) {
+                        layoutCalls++
+                        layoutLatch.countDown()
+                        val placeable = measurables[0].measure(constraints)
+                        placeable.place(
+                            (30.ipx - placeable.width) / 2,
+                            (30.ipx - placeable.height) / 2
+                        )
+                    }
+                })
             }
         }
-        assertTrue(layoutLatch.await(1, TimeUnit.SECONDS))
+        assertTrue(layoutLatch.await(10, TimeUnit.SECONDS))
 
         validateSquareColors(outerColor = blue, innerColor = white, size = 10)
 
diff --git a/ui/framework/src/main/java/androidx/ui/core/Text.kt b/ui/framework/src/main/java/androidx/ui/core/Text.kt
index 03ca3e0..bd55e4b 100644
--- a/ui/framework/src/main/java/androidx/ui/core/Text.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/Text.kt
@@ -36,6 +36,7 @@
 import androidx.compose.onCommit
 import androidx.compose.state
 import androidx.compose.memo
+import androidx.compose.onDispose
 import androidx.compose.unaryPlus
 
 private val DefaultTextAlign: TextAlign = TextAlign.Start
@@ -47,15 +48,7 @@
 /** The default selection color if none is specified. */
 private val DefaultSelectionColor = Color(0x6633B5E5)
 
-/**
- * Text Widget Crane version.
- *
- * The Text widget displays text that uses multiple different styles. The text to display is
- * described using a tree of [TextSpan] objects, each of which has an associated style that is used
- * for that subtree. The text might break across multiple lines or might all be displayed on the
- * same line depending on the layout constraints.
- */
-// TODO(migration/qqd): Add tests when text widget system is mature and testable.
+
 @Composable
 fun Text(
     /** How the text should be aligned horizontally. */
@@ -89,6 +82,71 @@
      */
     @Children child: @Composable TextSpanScope.() -> Unit
 ) {
+    val rootTextSpan = +memo { TextSpan() }
+    val ref = +compositionReference()
+    compose(rootTextSpan, ref, child)
+    +onDispose { disposeComposition(rootTextSpan, ref) }
+
+    // TODO This is a temporary workaround due to lack of textStyle parameter of Text.
+    val textSpan = if (rootTextSpan.children.size == 1) {
+        rootTextSpan.children[0]
+    } else {
+        rootTextSpan
+    }
+    Text(
+        textAlign = textAlign,
+        textDirection = textDirection,
+        softWrap = softWrap,
+        overflow = overflow,
+        textScaleFactor = textScaleFactor,
+        maxLines = maxLines,
+        selectionColor = selectionColor,
+        text = textSpan
+    )
+}
+
+/**
+ * Text Widget Crane version.
+ *
+ * The Text widget displays text that uses multiple different styles. The text to display is
+ * described using a tree of [TextSpan] objects, each of which has an associated style that is used
+ * for that subtree. The text might break across multiple lines or might all be displayed on the
+ * same line depending on the layout constraints.
+ */
+// TODO(migration/qqd): Add tests when text widget system is mature and testable.
+@Composable
+internal fun Text(
+    /** How the text should be aligned horizontally. */
+    textAlign: TextAlign = DefaultTextAlign,
+    /** The directionality of the text. */
+    textDirection: TextDirection = DefaultTextDirection,
+    /**
+     *  Whether the text should break at soft line breaks.
+     *  If false, the glyphs in the text will be positioned as if there was unlimited horizontal
+     *  space.
+     *  If [softWrap] is false, [overflow] and [textAlign] may have unexpected effects.
+     */
+    softWrap: Boolean = DefaultSoftWrap,
+    /** How visual overflow should be handled. */
+    overflow: TextOverflow = DefaultOverflow,
+    /** The number of font pixels for each logical pixel. */
+    textScaleFactor: Float = 1.0f,
+    /**
+     *  An optional maximum number of lines for the text to span, wrapping if necessary.
+     *  If the text exceeds the given number of lines, it will be truncated according to [overflow]
+     *  and [softWrap].
+     *  The value may be null. If it is not null, then it must be greater than zero.
+     */
+    maxLines: Int? = DefaultMaxLines,
+    /**
+     *  The color used to draw selected region.
+     */
+    selectionColor: Color = DefaultSelectionColor,
+    /**
+     * Composable TextSpan attached after [text].
+     */
+    text: TextSpan
+) {
     val context = composer.composer.context
     val internalSelection = +state<TextSelection?> { null }
     val registrar = +ambient(SelectionRegistrarAmbient)
@@ -106,21 +164,10 @@
         }
     }
 
-    val rootTextSpan = +memo { TextSpan() }
-    val ref = +compositionReference()
-    compose(rootTextSpan, ref, child)
-
-    // TODO This is a temporary workaround due to lack of textStyle parameter of Text.
-    val textSpan = if (rootTextSpan.children.size == 1) {
-        rootTextSpan.children[0]
-    } else {
-        rootTextSpan
-    }
-
     val style = +ambient(CurrentTextStyleAmbient)
-    val mergedStyle = style.merge(textSpan.style)
+    val mergedStyle = style.merge(text.style)
     // Make a wrapper to avoid modifying the style on the original element
-    val styledText = TextSpan(style = mergedStyle, children = mutableListOf(textSpan))
+    val styledText = TextSpan(style = mergedStyle, children = mutableListOf(text))
 
     Semantics(
         label = styledText.toPlainText()
@@ -223,10 +270,11 @@
         textDirection = textDirection,
         softWrap = softWrap,
         overflow = overflow,
-        maxLines = maxLines
-    ) {
-        Span(text = text, style = style)
-    }
+        textScaleFactor = 1.0f,
+        maxLines = maxLines,
+        selectionColor = DefaultSelectionColor,
+        text = TextSpan(text = text, style = style)
+    )
 }
 
 internal val CurrentTextStyleAmbient = Ambient.of<TextStyle>("current text style") {
diff --git a/ui/framework/src/main/java/androidx/ui/core/TextSpanCompose.kt b/ui/framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
index 642b735..d41059777 100644
--- a/ui/framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
+++ b/ui/framework/src/main/java/androidx/ui/core/TextSpanCompose.kt
@@ -34,10 +34,9 @@
  * when the [TextSpan] container is composed for the first time.
  */
 private class Root : Component() {
-    @Suppress("DEPRECATION")
-    fun update() = recomposeSync()
-
+    fun update() = composer.compose()
     lateinit var scope: TextSpanScope
+    lateinit var composer: CompositionContext
     lateinit var composable: @Composable() TextSpanScope.() -> Unit
     @Suppress("PLUGIN_ERROR")
     override fun compose() {
@@ -86,18 +85,16 @@
         lateinit var composer: TextSpanComposer
         root = Root()
         setRoot(container, root)
-
-        val cc = CompositionContext.prepare(root, parent) {
+        root.composer = CompositionContext.prepare(root, parent) {
             TextSpanComposer(container, this).also { composer = it }
         }
-        val scope = TextSpanScope(TextSpanComposition(composer))
-
-        root.scope = scope
+        root.scope = TextSpanScope(TextSpanComposition(composer))
         root.composable = composable
 
-        cc.compose()
+        root.update()
     } else {
         root.composable = composable
+
         root.update()
     }
 }
diff --git a/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt b/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt
index acd52de..0691fbe 100644
--- a/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt
+++ b/ui/material/integration-tests/material-demos/src/main/java/androidx/ui/material/demos/SelectionsControlsDemo.kt
@@ -39,6 +39,7 @@
 import androidx.compose.Composable
 import androidx.compose.Model
 import androidx.compose.composer
+import androidx.compose.memo
 import androidx.compose.state
 import androidx.compose.unaryPlus
 
@@ -127,9 +128,9 @@
 @Composable
 fun CheckboxDemo() {
     Column(crossAxisAlignment = CrossAxisAlignment.Start) {
-        val state = CheckboxState(Checked)
-        val state2 = CheckboxState(Checked)
-        val state3 = CheckboxState(Checked)
+        val state = +memo { CheckboxState(Checked) }
+        val state2 = +memo { CheckboxState(Checked) }
+        val state3 = +memo { CheckboxState(Checked) }
         fun calcParentState() = parentCheckboxState(state.value, state2.value, state3.value)
         val onParentClick = {
             val s = if (calcParentState() == Checked) {
diff --git a/ui/material/src/main/java/androidx/ui/material/MaterialTheme.kt b/ui/material/src/main/java/androidx/ui/material/MaterialTheme.kt
index 42fffd3..676cc7b 100644
--- a/ui/material/src/main/java/androidx/ui/material/MaterialTheme.kt
+++ b/ui/material/src/main/java/androidx/ui/material/MaterialTheme.kt
@@ -37,6 +37,7 @@
 import androidx.compose.ambient
 import androidx.compose.composer
 import androidx.compose.effectOf
+import androidx.compose.memo
 import androidx.compose.unaryPlus
 
 /**
@@ -223,17 +224,20 @@
 @Composable
 fun MaterialRippleTheme(@Children children: @Composable() () -> Unit) {
     val materialColors = +ambient(Colors)
-    val defaultTheme = RippleTheme(
-        factory = DefaultRippleEffectFactory,
-        colorCallback = { background ->
-            if (background == null || background.alpha == 0f ||
-                background.luminance() >= 0.5) { // light bg
-                materialColors.primary.copy(alpha = 0.12f)
-            } else { // dark bg
-                Color(0xFFFFFFFF.toInt()).copy(alpha = 0.24f)
+    val defaultTheme = +memo {
+        RippleTheme(
+            factory = DefaultRippleEffectFactory,
+            colorCallback = { background ->
+                if (background == null || background.alpha == 0f ||
+                    background.luminance() >= 0.5
+                ) { // light bg
+                    materialColors.primary.copy(alpha = 0.12f)
+                } else { // dark bg
+                    Color(0xFFFFFFFF.toInt()).copy(alpha = 0.24f)
+                }
             }
-        }
-    )
+        )
+    }
     CurrentRippleTheme.Provider(value = defaultTheme, children = children)
 }
 
diff --git a/ui/material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt b/ui/material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
index fe4e961..f26f84f 100644
--- a/ui/material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
+++ b/ui/material/src/main/java/androidx/ui/material/ripple/RippleSurface.kt
@@ -18,20 +18,21 @@
 
 import androidx.annotation.CheckResult
 import androidx.ui.animation.transitionsEnabled
+import androidx.ui.core.Draw
 import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.OnPositioned
 import androidx.ui.core.toRect
 import androidx.ui.graphics.Color
 import androidx.compose.Ambient
 import androidx.compose.Children
 import androidx.compose.Composable
-import androidx.compose.Model
-import androidx.compose.composer
+import androidx.compose.Recompose
 import androidx.compose.ambient
+import androidx.compose.composer
 import androidx.compose.effectOf
+import androidx.compose.invalidate
 import androidx.compose.memo
 import androidx.compose.unaryPlus
-import androidx.ui.core.Draw
-import androidx.ui.core.OnPositioned
 
 /**
  * An interface for creating [RippleEffect]s on a [RippleSurface].
@@ -95,16 +96,19 @@
     owner.backgroundColor = color
 
     OnPositioned(onPositioned = { owner._layoutCoordinates = it })
-    Draw { canvas, size ->
-        // TODO(Andrey) Find a better way to disable ripples when transitions are disabled.
-        val transitionsEnabled = transitionsEnabled
-        if (owner.effects.isNotEmpty() && transitionsEnabled) {
-            canvas.save()
-            canvas.clipRect(size.toRect())
-            owner.effects.forEach { it.draw(canvas) }
-            canvas.restore()
+    Recompose { recompose ->
+        owner.recompose = recompose
+
+        Draw { canvas, size ->
+            // TODO(Andrey) Find a better way to disable ripples when transitions are disabled.
+            val transitionsEnabled = transitionsEnabled
+            if (owner.effects.isNotEmpty() && transitionsEnabled) {
+                canvas.save()
+                canvas.clipRect(size.toRect())
+                owner.effects.forEach { it.draw(canvas) }
+                canvas.restore()
+            }
         }
-        owner.recomposeModel.registerForRecomposition()
     }
     CurrentRippleSurface.Provider(value = owner, children = children)
 }
@@ -115,14 +119,12 @@
     override val layoutCoordinates
         get() = _layoutCoordinates
             ?: throw IllegalStateException("The surface wasn't yet positioned!")
-    internal var recomposeModel = RecomposeModel()
+    internal var recompose: () -> Unit = {}
 
     internal var _layoutCoordinates: LayoutCoordinates? = null
     internal var effects = mutableListOf<RippleEffect>()
 
-    override fun markNeedsRedraw() {
-        recomposeModel.recompose()
-    }
+    override fun markNeedsRedraw() = recompose()
 
     override fun addEffect(feature: RippleEffect) {
         assert(!feature.debugDisposed)
@@ -137,19 +139,3 @@
         markNeedsRedraw()
     }
 }
-
-// TODO(Andrey: Temporary workaround for the ripple invalidation)
-@Model
-private class RecomposeModel {
-
-    private var ticker = 0
-
-    fun recompose() {
-        ticker++
-    }
-
-    fun registerForRecomposition() {
-        @Suppress("UNUSED_VARIABLE")
-        val ticker = ticker
-    }
-}