Hello, how can I call `onGloballyPositioned` insid...
# compose
g
Hello, how can I call
onGloballyPositioned
inside
Modifier.composed
? That inside modifier doesn’t seem to be called!
Copy code
fun Modifier.tooltip(
    scope: CoroutineScope,
    tooltipHostState: TooltipHostState,
    text: String
) = composed {
    var layoutCoordinates by remember { mutableStateOf<LayoutCoordinates?>(null) }
    onGloballyPositioned {
        layoutCoordinates = it
        logger().d("tooltip", "onGloballyPositioned = $layoutCoordinates")
    }

    LaunchedEffect(layoutCoordinates) {
        logger().d("tooltip", "LaunchedEffect = $layoutCoordinates")
        scope.launch {
            tooltipHostState.showTooltip(text)
        }
    }
    return@composed this
}
What I am trying to do is to launch only once (LaunchedEffect) when
onGloballyPositioned
is called 🤔
f
You need to return the modifier created from
onGloballyPositioned
. Right now your
tooltip
modifier takes the chain, doesn't add anything and returns it.
e
Also, you shouldn’t need to launch two coroutines 🙂 (ie scope.launch inside an already launched effect)
👆 1
g
@efemoney Right that was wrong
@Filip Wiesner So, like that?
Copy code
fun Modifier.tooltip(
    tooltipHostState: HzTooltipHostState,
    text: String
) = composed {
    var layoutCoordinates by remember { mutableStateOf<LayoutCoordinates?>(null) }

    LaunchedEffect(layoutCoordinates) {
        logger().d("tooltip", "LaunchedEffect = $layoutCoordinates")
        tooltipHostState.showTooltip(text)
    }

    return@composed this.then(onGloballyPositioned {
        layoutCoordinates = it
        logger().d("tooltip", "onGloballyPositioned = $layoutCoordinates")
    })
}
It doesn’t seem the LaunchedEffect here runs only at every change of the LayoutCoordinates object as it’s the same ref all the time:
Copy code
2022-03-24 12:35:12.950 30632-30632/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:35:12.957 30632-30632/app D/tooltip: LaunchedEffect = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:35:13.004 30632-30632/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:35:13.009 30632-30632/app D/tooltip: LaunchedEffect = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:35:13.274 30632-30632/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
e
onGloballyPositioned { … }
should be fine (no need for
this.then(...)
), Also you have two log statements so that would be twice the logs and finally, onGloballyPositioned callback is called many many times, it does not have any guarantees around being distinct and that should be the cause for most of the log statements
f
Yes but you don't need to explicitly
return
and call
this.then()
. Just let the modifier be the last statement that get's returned. And for the recomposition, I am not sure why on first look. I would have to dig more but you shouldn't really care about recomposing too often. The problems start when it does not recompose and it should 😉
it does not have any guarantees around being distinct and that should be the cause for most of the log statements
Yes but the state should change only when equality changes and
LaunchedEffect
has it as key. Maybe I am missing something obvious.
e
In this case I would care about recompositions because based on basic understanding of compose it shouldn’t happen in this scenario (ie LaunchedEffect should be relaunched only when layoutCoords are distinctly changed). I’d also be alarmed to see that many log statements. The function is not recomposing, the callback is just being called multiple times and the log statements are identical
g
Got it to work!
Copy code
fun Modifier.tooltip(
    tooltipHostState: HzTooltipHostState,
    text: String
) = composed {
    var layoutCoordinates by remember { mutableStateOf<LayoutCoordinates?>(null) }

    if(layoutCoordinates != null) {
        LaunchedEffect(layoutCoordinates) {
            logger().d("tooltip", "LaunchedEffect = $layoutCoordinates")
            tooltipHostState.showTooltip(text)
        }
    }

    onGloballyPositioned {
        layoutCoordinates = it
        logger().d("tooltip", "onGloballyPositioned = $layoutCoordinates")
    }
}
When null it was also being called, now the
LaunchedEffect
is really called once per change of that object ref. Thanks!
🎉 1
f
I mean, for every
onGloballyPositioned
trigger we get a
LaunchedEffect
trigger even though it looks like the same instance
🤔 @efemoney
🚫 1
Oh, nice 🎉
g
@Filip Wiesner It was the case because of nullability apparently
e
Awesome @galex 😅
g
Wrong Alex but I appreciate it :-D
f
OH! It was the modifier instance not the
LayoutCoordinates
instance in the logs, my bad 🤦‍♂️
g
Thank you both for helping @efemoney @Filip Wiesner 😊
🙌 1
🦜 1
🙌🏼 1
e
@galex qq, do you still see a lot of logs (or is it at most 2 or 3)?
g
@efemoney Totally
Copy code
2022-03-24 12:47:25.882 1245-1245/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:47:25.902 1245-1245/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:47:25.907 1245-1245/app D/tooltip: LaunchedEffect = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:47:25.945 1245-1245/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
2022-03-24 12:47:26.198 1245-1245/app D/tooltip: onGloballyPositioned = androidx.compose.ui.node.OnGloballyPositionedModifierWrapper@259780
e
Yup I guessed so. if you change the log tag for the oGP callback to be
tooltip-oGP
and for the launched effect to be
tooltip-lE
you would notice most of the logs are from oGP.
g
Yes you can see that there are 4 from the oGP and only one now from the LE
👍🏼 1
z
The usual modifier chaining requirements don't actually apply inside
composed
. You actually don't need
this.then
. The receiver is already a
Modifier
so you can just directly call modifier factory functions. But that receiver doesn't even matter so you can even return
Modifier.foo()
. The returned modifier will always be chained anyway.
Probably a better way to do this though would be to launch the coroutine directly from onGloballyPositioned. Otherwise almost every layout pass is going to go back and trigger a recomposition, which won't happen until the next frame, and for no other reason than to launch a coroutine that could have been launched immediately.
1
You can use rememberCoroutineScope.
g
Interesting, thanks I will try that @Zach Klippenstein (he/him) [MOD] 🙂