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

galex

03/24/2022, 10:23 AM
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

Filip Wiesner

03/24/2022, 10:26 AM
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

efemoney

03/24/2022, 10:29 AM
Also, you shouldn’t need to launch two coroutines 🙂 (ie scope.launch inside an already launched effect)
👆 1
g

galex

03/24/2022, 10:35 AM
@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

efemoney

03/24/2022, 10:44 AM
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

Filip Wiesner

03/24/2022, 10:44 AM
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

efemoney

03/24/2022, 10:47 AM
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

galex

03/24/2022, 10:48 AM
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

Filip Wiesner

03/24/2022, 10:48 AM
I mean, for every
onGloballyPositioned
trigger we get a
LaunchedEffect
trigger even though it looks like the same instance
🤔 @efemoney
🚫 1
Oh, nice 🎉
g

galex

03/24/2022, 10:49 AM
@Filip Wiesner It was the case because of nullability apparently
e

efemoney

03/24/2022, 10:49 AM
Awesome @galex 😅
g

galex

03/24/2022, 10:50 AM
Wrong Alex but I appreciate it :-D
f

Filip Wiesner

03/24/2022, 10:50 AM
OH! It was the modifier instance not the
LayoutCoordinates
instance in the logs, my bad 🤦‍♂️
g

galex

03/24/2022, 11:00 AM
Thank you both for helping @efemoney @Filip Wiesner 😊
🙌 1
🦜 1
🙌🏼 1
e

efemoney

03/24/2022, 11:01 AM
@galex qq, do you still see a lot of logs (or is it at most 2 or 3)?
g

galex

03/24/2022, 11:02 AM
@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

efemoney

03/24/2022, 11:03 AM
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

galex

03/24/2022, 11:06 AM
Yes you can see that there are 4 from the oGP and only one now from the LE
👍🏼 1
z

Zach Klippenstein (he/him) [MOD]

03/24/2022, 3:08 PM
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

galex

03/28/2022, 11:21 AM
Interesting, thanks I will try that @Zach Klippenstein (he/him) [MOD] 🙂
43 Views