https://kotlinlang.org logo
s

Se7eN

02/12/2021, 2:45 PM
[Code in thread 👇] I have this code where I'm using the
pointerInput
modifier to allow dragging inside my
Box
. The problem is when the
locked
parameter (and other parameters) change,
onPositionChange
and
onDragStateChange
still executes with the old value of the parameters. Like if
locked
was true by default and the state changes to false, the if statement in
onPositionChange
will still run. I don't know it might be related to some kotlin magic but I was using the now deprecated
dragGestureFilter
modifier before alpha12 and it was working fine with the same code. I've also tried using
detectDragGestures
inside
pointerInput
but no luck.
Copy code
fun Parent() {
    val locked by remember { mutableStateOf(true) }
    MyComposable(locked, ...)
}

fun MyComposable(locked: Boolean, ...) {
    val inputModifier = Modifier.simpleDragInput(
        onDragStateChange = { dragging -> ... },
        onPositionChange = { dragPosition ->
            if(locked) {
                ...
            }
        }
    )

    Box(modifier = inputModifier)
}

fun Modifier.simpleDragInput(
    onPositionChange: (Offset) -> Unit,
    onDragStateChange: (Boolean) -> Unit,
): Modifier = pointerInput(Unit) {
    forEachGesture {
        awaitPointerEventScope {
            val down = awaitFirstDown()
            onDragStateChange(true)
            onPositionChange(down.position)
            drag(down.id) { change ->
                change.consumePositionChange(
                    change.position.x - change.previousPosition.x,
                    change.position.y - change.previousPosition.y
                )
                onPositionChange(change.position)
            }
            onDragStateChange(false)
        }
    }
}
v

Vsevolod Ganin

02/12/2021, 2:51 PM
This is because
pointerInput(Unit)
is remembering your first passed lambda for composition lifetime. Your should pass appropriate keys to make it update lambda
Every instance of lambda captures local variables in turn. So your first `pointerInput`’s lambda remembers first
onPositionChange
and
onDragStateChange
and they remember first seen
locked
value
s

Se7eN

02/12/2021, 2:53 PM
Ah I see. So I should pass all my parameters to pointerInput I'm using inside my lambdas? I have like 5-6 parameters.
v

Vsevolod Ganin

02/12/2021, 2:54 PM
I guess so, yes
s

Se7eN

02/12/2021, 2:54 PM
Alright thanks
v

Vsevolod Ganin

02/12/2021, 2:56 PM
It’s the same story with
DisposableEffect
and
LaunchedEffect
. Moreover this behavior is due to the fact that
pointerInput
relies on
LaunchedEffect
in its implementation
s

Se7eN

02/12/2021, 3:10 PM
Okay I added all my parameters but now the drag doesn't work properly. It only moves the drag position when I start the drag but after that the drag just stops working
v

Vsevolod Ganin

02/12/2021, 3:15 PM
I experienced similar problems, yes. It was because now my lambda was updating on every recomposition, cancelling previous efforts. Turned out, it was because of keys that I forgot to wrap with
remember
in the first place, so they were always new on every recomposition. So try to inspect each key if it is different on every recomposition
👍 1
m

manueldidonna

02/12/2021, 3:18 PM
Try to pass your properties to rememberUpdatedState. It's an API to publish data from recomposition to ongoing or long-lived processes such as Disposable/LaunchedEffects
👍🏻 2
1
s

Se7eN

02/12/2021, 3:29 PM
Still the same thing after passing keys with and without
rememberUpdatedState
. I do have a list key that is updated inside
onPositionChanged
using the drag position. I'll try making a minimal reproducible example
a

Adam Powell

02/12/2021, 3:58 PM
The parameters to
pointerInput
determine when your detector state machine will reset, and
rememberUpdatedState
lets you update things like these callbacks out from under the running
pointerInput
state machine without resetting it. The pattern @manueldidonna is describing above is:
Copy code
fun Modifier.simpleGesture(
  onGesture: () -> Unit
): Modifier {
  val currentOnGesture by rememberUpdatedState(onGesture)

  // Unit because we do not want to restart
  return pointerInput(Unit) {
    // ...
    // when detected, call currentOnGesture, not onGesture.
    // currentOnGesture is kept up to date by composition.
    currentOnGesture()
  }
}
❤️ 2
s

Se7eN

02/12/2021, 4:02 PM
Got it, this works. Thanks everyone!
👍 3
One more thing: When should the state machine reset?
a

Adam Powell

02/12/2021, 4:06 PM
It depends on the use case. Possibly never, possibly when something deeply significant to the gesture changes such that old input in progress is invalid and things should be cancelled.
👍 1
A little hand-wavey, I know, sorry, I'm only on my first cup of coffee for the day 😅
s

Se7eN

02/12/2021, 4:08 PM
Haha it's okay I get the general idea
👍 1
2 Views