if I do like this, Surface will always recompose ....
# compose
n
if I do like this, Surface will always recompose .but if I use thread or dispatch another thread Surface will not recompose .Why ?
Copy code
Surface {
    darkMode.value = !darkMode.value
}
this way not recompose:
Copy code
Surface {
    thread {
        darkMode.value = !darkMode.value
    }
}
🤯 4
🙅 3
🙅🏼 1
a
You shouldn't do either one of these in real code 🙂 here's why it behaves this way:
By reading
darkMode.value
in the right hand side of the expression in a composable function body in snippet 1, you've told compose that the correct answer for that code depends on
darkMode.value
as an input. You then immediately write
darkMode.value
.
Compose always wants to recompose something if its inputs changed.
Semantically speaking, you've told compose, "this statement is false" and asked it to ponder the philosophical implications of that declaration. 🙂
Neither code snippet is idempotent; both introduce side effects of composition that produce different results depending on how many times the function recomposes.
The second snippet launches a new thread as an uncontrolled side effect and performs the read and write there; compose has no indication that the read or write have something to do with the recompose scope that launched the thread.
n
Copy code
Surface {
    lifecycleScope.launch {
        delay(1000)
        darkMode.value = !darkMode.value
    }
}
if add
delay
function it will not recompose ,or will always recompose 😂 , always recompose I understand, but after suspend and not recompose I need take some time to understand☹️
a
If
lifecycleScope
is coming from a
LifecycleOwner
then you've encountered the reason I dislike immediate coroutine dispatchers 🙂
At some point
lifecycleScope
was changed to use
Dispatchers.Main.immediate
which will run that code to first suspension undispatched, which means compose will track the read.
But once again we're talking about the mechanism, best practices would tell you to never
coroutineScope.launch {}
from a composable function body either, you should use the declarative
LaunchedEffect
instead.
(I think we have a lint warning that will scold you for this code now)
🙌 2
d
Is the lint warning in android studio or the compiler plugin?
a
Android lint, so neither, but studio will enable it by default in the ide
👍 1
g
the reason I dislike immediate coroutine dispatchers
Same here, I was surprised when immediate dispatcher was used by default on lifecycleScope, it caused some bugs on our side after update and in general immediate dispatcher behaviour is much more tricky and doesn’t worth this optimization imo
n
Do you know where to look at this part of the source code? 🥺
a
@gildor I don't agree with the use of an immediate dispatcher there either, but in its defense the reason it was done was for timing of avoiding a lifecycle state change before the dispatched continuation runs, not optimization. I'm glad that
CoroutineStart.UNDISPATCHED
graduated to stable recently since that almost always addresses whatever lifecycle+timing issue people tend to reach for an immediate dispatcher to solve, but without the unexpected reentrance pitfalls.
@Nana Vong which part of what source code?
n
@Adam Powell In
thread
and
delay
case , whether Composer has check call stack frame to choose recompose ?
a
the recomposition process takes a mutable snapshot using the snapshot API and installs read and write callbacks for that snapshot. It enters that snapshot to perform recomposition, which causes those callbacks to be invoked on snapshot state access. https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]/androidx/compose/runtime/Recomposer.kt;l=788?q=Recomposer.kt
The snapshot system knows the current snapshot by way of a form of ThreadLocal; it doesn't access the call stack frame, it accesses that special ThreadLocal to determine the current snapshot.
The current call stack is only relevant in that the caller of recomposition installs and removes the recomposition snapshot (and its associated access callbacks) as the thread's current snapshot before and after calling recomposition.
so if the snapshot state read isn't performed during that call to recompose itself, on that calling thread, it isn't recorded as an input value of the composition.
👍 1
And if it's not recorded as an input value of composition, changing it later will have no effect on that composition.
n
Thank u very much, I think my understanding of Compose is getting better🤝
👍 1