https://kotlinlang.org logo
#compose
Title
# compose
d

dimsuz

12/16/2021, 5:39 PM
this bit of code never prints "1" in console. Does recomposer conflate values?
Copy code
fun main() {
  var state by mutableStateOf(value = "30")
  application {
    Window {
      Button(onClick = { state = "1"; state = "2"}) {
        println("got $state")
        Text(state)
      }
    }
  }
}
Debugger shows that shapshot system sees all values, but
recompositionRunner
never receives the "1" value. Are there some optimizations in
Snapshot.apply
method maybe? What should one do to receive all state updates (or is this impossible/wrong)?
z

Zach Klippenstein (he/him) [MOD]

12/16/2021, 8:15 PM
Changing a mutable state read by a composition only schedules a recomposition to happen before the next frame. Any number of changes to any number of state objects can happen before then.
It is indeed a bit wrong to try to force something to see all state updates. States should be idempotent.
2
d

dimsuz

12/16/2021, 8:46 PM
I see! thank you for clarification!
We have this curious situation: imagine entering pin-code and then requesting to enter it again to confirm. for each pin digit we display a filled dot. When user fills all dots of 1st pin UI switches to new pin state, fresh, empty. And this state change happens so fast that user doesn't see the last dot filled, it goes from
***o
->
oooo
while state goes
Copy code
digits = [1,1,1,0]
digits = [1,1,1,1]
digits = [0,0,0,0]
last two get "conflated" because, as you've explained they fit into one frame update. While we would like that user would see all three states rendered, including the fully filled one. I wonder what would the correct solution be here. We could always add some arbitrary delay, but this doesn't feel right.
@Adam Powell ^^^ I recall that maybe you had once explained something about these cases, but don't remember exactly what. Isn't this somehow related to states vs events discussion?
a

Alex Vanyo

12/16/2021, 9:47 PM
we would like that user would see all three states rendered, including the fully filled one.
How long would you want the user to see the fully filled state?
z

Zach Klippenstein (he/him) [MOD]

12/16/2021, 10:11 PM
yea this sounds like a question for your designer. Animation could help here
👍 3
a

Alex Vanyo

12/16/2021, 10:14 PM
Also my question is meant to be leading 😄 , since my follow up question will be this: If you want the user to see the fully filled state for (say) 500ms, what happens if they enter the first digit of the pincode after just 200ms? Then what do you want to be displayed?
👏 1
d

dimsuz

12/16/2021, 10:14 PM
Yeah, in the end we (devs) seem to came to the same conclusion, that this evidently needs some animation/delay, because this isn't anymore strictly about the data state, but also about interaction state. Initially this has tricked us, because the "fill" events came from user pressing keys, but the last one triggers the transition which is programmatic, but we want it to feel interactive. Not sure if I explain myself correctly 🙂
what happens if they enter the first digit of the pincode after just 200ms
right! I've also thought in this direction, that if this has to be a delay, it has to be "smart" 🙂
a

Alex Vanyo

12/16/2021, 10:29 PM
I think there’s a nifty approach here involving `MutatorMutex`:
Copy code
class PasscodeState {
    var firstDigit by mutableStateOf(false)
        private set
    var secondDigit by mutableStateOf(false)
        private set
    var thirdDigit by mutableStateOf(false)
        private set
    var fourthDigit by mutableStateOf(false)
        private set

    private val mutatorMutex = MutatorMutex()

    suspend fun inputDigit() {
        mutatorMutex.mutate {
            if (!firstDigit) {
                firstDigit = true
            } else if (!secondDigit) {
                secondDigit = true
            } else if (!thirdDigit) {
                thirdDigit = true
            } else {
                fourthDigit = true
                try {
                    delay(500)
                } finally {
                    firstDigit = false
                    secondDigit = false
                    thirdDigit = false
                    fourthDigit = false
                }
            }
        }
    }
}
How that works is there can only be at most one instance of the
block
passed to
MutatorMutex.mutate
running at a time. Just like a normal mutex, it is guarding setting any of the digit state.
MutatorMutex
also has the feature that a second call while there is an existing one running will cancel the old call (the priority controls exactly when). So in this case, when we set the fourth digit to true, we plan to reset them all after 500 milliseconds. If another input comes in earlier, the
finally
triggers first, resetting the state, before the new input runs and sets the
firstDigit
to
true
again.
1
a

Adam Powell

12/16/2021, 10:37 PM
suspend is so much more fun once you realize it's a language feature for generating arbitrary state machines 😄
☝🏼 1
👍 1
☝️ 2
👍🏼 1
And yeah, this is related to the states vs. events space. Zach started another internal discussion in response to this thread too and the topic of the
InteractionState
API came up - we modeled that as an event stream designed to reduce state specifically to enable this sort of thing; lingering pressed states, etc. though we aren't leveraging a whole lot of it today
the code for handling press states for Views is kind of complicated and annoying for all of these same reasons
d

dimsuz

12/16/2021, 11:36 PM
Thank you all for the input! Fisrt time I've heard about
MuatatorMutex
, will study it; and that "finally" trick is cool. Yeah, suspend functions are the state machines underneath, aren't they 🙂 I guess first we learn to "forget" this abstraction, but then we learn about it on kind-of a new layer.
👍 1