Hey everyone! So I'm having this problem and I'm n...
# compose
a
Hey everyone! So I'm having this problem and I'm not sure what the "conventions" are in this situation, I was trying to create a range bar, a simplified version of which is this:
Copy code
@Composable
fun SomeRangeBar(
    lower: Float,
    upper: Float,
    onLowerChanged: (Float) -> Unit,
    onUpperChanged: (Float) -> Unit,
    range: ClosedRange<Float>,
    modifier: Modifier = Modifier
) {
    Canvas(modifier = modifier.pointerInput(Unit) {
        detectHorizontalDragGestures {
            if (headToMove == Head.LOWER) {
                onLowerChanged(
                    newValue.coerceIn(range.start, upper)
                )
            } else {
                onUpperChanged(
                    newValue.coerceIn(lower, range.endInclusive)
                )
            }
        }
    }) {
    // drawing...
    }
}

@Composable
fun UseSomeRangeBar() {
    val lower by remember { mutableStateOf(25f) }
    val upper by remember { mutableStateOf(100f) }
    SomeRangeBar(
        lower = lower,
        upper = upper,
        onLowerChanged = { lower = it },
        onUpperChanged = { upper = it },
    )
}
The problem would be that, since the
pointerInput
closure survives recomposition and since it also captures the values of
lower
and
upper
then the values of
lower
and
upper
passed to
coerceIn
are always the initial lower and upper values, all subsequent values of
lower
and
upper
never make it to the lambda. A simple solution to this is to pass
MutableState<Float>
instead of
Float
as
lower
and
upper
and since
MutableState
is mutable lol, then updates to
lower
and
upper
make it to the closure:
Copy code
@Composable
fun SomeRangeBar(
    lower: State<Float>,
    upper: State<Float>,
    //...
) {
//...
}

@Composable
fun UseSomeRangeBar() {
    val lower = remember { mutableStateOf(25f) }
    val upper = remember { mutableStateOf(100f) }
    SomeRangeBar(
        lower = lower,
        upper = upper,
        onLowerChanged = { lower.value = it },
        onUpperChanged = { upper.value = it },
    )
}
But I haven't seen anything like this in the available composables, what's the convention in such a situation?
d
There's
rememberUpdatableState
which is built for this I think.
💯 1
Although it's a bit odd for the closure to survive recomposition when state changes, that sounds like a bug IMO. Edit: Not a bug, just didn't look at the docs lol.
Also, large code snippets in 🧵 next time.
🙌 1
a
rememberUpdatedState
seems to do the trick, Thank you!
pointerInput
uses
LaunchedEffect
which I believe create a coroutine which is only restarted when the key passed is changed, so although being counterintuitive I believe it is the desired behavior. I tried passing a random UUID as a key and it started doing some weird shenanigans, the dragging was forcibly ended on each recomposition. Also, sorry about the large snippet thing.
d
Interesting, and why don't you do
modifier.pointerInput(lower, upper)
?
a
Since
upper
and
lower
are constantly updated while the dragging is happening, not just when it ends, and since their values changing is what causes recomposition, it would be similar to passing a UUID as a key, where the dragging is forcibly ended on the first update to either of them.
I just tried it and it gave the same result as the UUID.
d
Ah fair enough