Is there a reason `Modifier.offset` causes recompo...
# compose
c
Is there a reason
Modifier.offset
causes recomposition infinitely when I'm not touching the screen? I'm using it together with
animateIntOffset
+ user scroll. Code in 🧵
Copy code
var fingerUp by rememberSaveable { mutableStateOf(true) }
    val offsetY = rememberSaveable { mutableStateOf(0f) }
    val extraLayoutHeight = rememberSaveable { mutableStateOf(0) }

val snapTransition = updateTransition(targetState = fingerUp, label = "")
val snapOffset by snapTransition.animateIntOffset(label = "", transitionSpec = { tween(if (fingerUp) 200 else 0) }) {
    if (it) {
        if (uiState.progress <= .5f) {
            IntOffset(0, 0)
        } else {
            IntOffset(0, extraLayoutHeight.value)
        }
    } else {
        IntOffset(0, offsetY.value.toInt())
    }
}

Column(
    modifier = Modifier
        .fillMaxWidth()
        .offset { snapOffset }
I could post the code below that changes some of the mutableStates above, but in debugging it doesn't hit anything below setting the offset, which seems to cause recomposition over and over (because it's an animation I assume).
d
Does
uiState.progress
change when you see recomposition? Reading that state in animation will recompose the animation whenever
uiState.progress
is changed, even though the use of the value is more coarse grained/threshold based. If you could post a minimal repro case, I'm happy to help investigate. 🙂
c
It doesn't 😕 I use
pointerInput
and
detectVerticalDragGestures
and set the progress in there, but this is causing an infinite recomposition cycle even with my fingers off the screen. I can try and setup a sample project if you give me a couple hours! 🙂
I ended up solving what I wanted to do by doing the animation manually based on recomposition but I think you'd get mad at me if you saw it 😆
d
Haha, no worries. I'd be curious to see the repro case. Maybe I'll suggest a different way to achieve this. 🙂
c
That'd be awesome if you could! It's basically a collapsingToolbar-like behavior with scrolling and then animate the snap to collapsed/expanded based on progress %
Unfortunately I'll have to post the sample tomorrow. Got swamped with other things 😢. I'll let you know when it's up!
d
No worries!
🙏 1
c
Okay! So I put something together to try to mimic my problem as closely as possible, but in doing so realized the infinite composition I was seeing was because of my timer I had running lol. It was updating my uiState every second. I'm still kind of curious why
Modifier.offset
is getting hit on recomposition though since the only thing I change is the text. Here's the project if you wanna take a look! If you have any suggestions they'd be greatly appreciated 🙇 https://github.com/cj1098/dragSample
d
Hey Chris, thanks for sharing. The animation is reading a progress value from
uiState
. Since the
uiState
gets updated every second with the ticker, therefore animation recomposes when the value of
uiState
changes. My local logging is showing recomposition ~once per second. That's what we expected. 🙂 For your particular use case, it'd be easier to manage the motion handoff from gesture to animation using an
Animatable
. You could fire up the animation in
onDragEnd
, as opposed to managing it separately in a state-based animation. You might find the "Gesture Animation" section in this codelab helpful: https://developer.android.com/codelabs/jetpack-compose-animation#7
c
Fascinating. I haven't worked with
Animatable
yet so I'm excited to explore that! I didn't know that it would cause recomposition of the animation if the value being read inside the animation wasn't getting changed. I think the only value was
uiState.bottomNavScrollProgress
and that got changed once in onDragEnd. So if an animation is reading a value from a snapshot state, it doesn't matter if that particular value it was reading changed or not, it will recompose because the value of
uiState
changes?
d
So if an animation is reading a value from a snapshot state, it doesn't matter if that particular value it was reading changed or not, it will recompose because the value of 
uiState
 changes?
The animation is effectively reading
foo.bar
, if
foo
gets updated to a new instance, animation would have to recompose to read from the new foo for
foo.bar
. If the value of
foo.bar
didn't change and the animation isn't running, the animation value should remain the same. This means whoever reads animation value won't be recomposed. 🙂
c
Wow thank you for that explanation! That really helped me get my mind around it. I think my breakpoints were making me think it was getting recomposed when really it wasn't. I'll take a look at
Animatable
now :)
👍 1
Hey! Long shot you'll see this but I was wondering if there's a way to prevent composables from taking touch gestures if another composable is scrolling? So for example in the project I sent you the parent composable has 8 FloatingActionButtons that each have their own click, but when the first ACTION_MOVE hits I want to relinquish their touch control over to the scroll if that makes sense..
g
I think this should happen automatically. The scroll should consume the ACTION_MOVE, and that should cause the button to cancel itself. Let me try to replicate it in a simple test project.
I tried this without any problems:
Copy code
LazyColumn {
    repeat(100) {
        item {
            Button(onClick = {}) {
                Text("Hello $it This is a nice button, eh?")
            }
        }
    }
}
Do you think it is a problem with FAB?
c
Possibly 🤔 I did figure out a solution and that was to use
detectVerticalDragGestures
instead of handling things manually via
awaitPointerEventScope
+
verticalDrag
I didn't try it with buttons, but I did try it with a clickable Image and that had the same problem. Meaning I added
Modifier.clickable
Btw I'm watching your and Nick's presentation on custom layouts. Thanks for making that! Learning lots of new things.
g
Glad you like it! You can look at the implementation of
detectVerticalDragGestures()
to see if it is doing something different than your implementation.
👍 1
c
Will do! Now I gotta figure out how to add fling for something as tall as 250 pixels >.<
😆 1