does compose have any API to queue an action on ne...
# compose
a
does compose have any API to queue an action on next composition? I want to pass focus to an item which is not yet added to the composition. Code in 🧵
Copy code
var isVisible by remember { mutableStateOf(false) }
    val focusRequester = remember { FocusRequester() }

    Row {
        BasicText("Show", modifier = Modifier.clickable {
            isVisible = true
            focusRequester.requestFocus() // <- 💥 EXCEPTION:  FocusRequester is not initialized
        })
        if(isVisible){
            Box(Modifier.focusRequester(focusRequester).clickable { /* TODO */ })
        }
    }
z
You can use
withNextFrameMillis
to wait until after the next frame is done
a
i like that. where do i get that value from?
Can't find
withNextFrameMillis
but there is a
withFrameMillis {}
. Trying to figure out how to use that now
Made it work. I keep finding a variant of this snippet online, which calculates the frame delta:
Copy code
@Composable
fun frameTimeMillis(): State<Long> {
    val millisState = remember { mutableStateOf(0L) }

    LaunchedEffect(Unit) {
        val startTime = withFrameMillis { it }

        while (true) {
            withFrameMillis { frameTime ->
                millisState.value = frameTime - startTime
            }
        }
    }

    return millisState
}
Not perfect for my usecase as keeping
val frameDelta by frameTimeMillis()
around seems to be causing compositions all the time (which makes sense). got something to work with, so all is good. Thanks Zach 👍
z
oops sorry, meant
withFrameMillis
. And yea, calling it will always request another frame.
But for the use case you mentioned in your original post, you don’t need to:
Copy code
Modifier.clickable {
  isVisible = true
  coroutineScope.launch {
    withFrameMillis {}
    focusRequester.requestFocus()
  }
}
a
@Zach Klippenstein (he/him) [MOD] i just tried it but the exception is still thrown My bad. Seems like it is working on the sample app/code i shared, but crashes on my real implementation. Will dig further tomorrow
@Zach Klippenstein (he/him) [MOD] So it crashes if the component with the focusRequester is within a
Popup
, but works great if it is just a
Box
. Are Popups special somehow? .
Copy code
var isVisible by remember { mutableStateOf(false) }
    val focusRequester = remember { FocusRequester() }
    val scope = rememberCoroutineScope()

    Row {
        BasicText("Show", modifier = Modifier.clickable {
            isVisible = true
            scope.launch {
                withFrameMillis { }
                focusRequester.requestFocus() // <- 💥 CRASH
            }
        })
        if (isVisible) {
            Popup {
                Box(Modifier.focusRequester(focusRequester).clickable { /* TODO */ }) {
                    BasicText("Hello")
                }
            }
        }
    }
z
Oh yea, there are some special things. I think this would be pretty awkward to work around, unless @Ralston Da Silva has any ideas. I feel like maybe
FocusRequester
should queue requests made before it’s ready to handle things like this more ergonomically.
t
Do you need
focusRequester
at the top level? Otherwise this works for me:
Copy code
var isVisible by remember { mutableStateOf(false) }

Row {
    BasicText("Show", modifier = Modifier.clickable {
        isVisible = true
    })

    if (isVisible) {
        Popup {
            val focusRequester = remember { FocusRequester() }
            LaunchedEffect(focusRequester) { focusRequester.requestFocus() }

            Box(Modifier.focusRequester(focusRequester).clickable { /* TODO */ }) {
                BasicText("Hello")
            }
        }
    }
}
1
r
I think adding the focusRequester to the Popup is the right approach.
a
@Tobias Suchalla your solution changes the original behavior. i dont want to focus the first item whenever the popup is displayed, but rather focus on it conditionally
@Ralston Da Silva what do you mean by this? Popups don't get modifiers
t
How is it differrent? Your example shows and then immediately focuses the Box, my solution does the very same. If you instead need to focus seperately:
Copy code
var isVisible by remember { mutableStateOf(false) }
    var shouldFocus by remember { mutableStateOf(false) }

    Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        BasicText("Show", Modifier.clickable { isVisible = true })
        BasicText("Hide", Modifier.clickable { isVisible = false })
        BasicText("Focus", Modifier.clickable { shouldFocus = true })
        BasicText("Show and focus", Modifier.clickable { 
            isVisible = true
            shouldFocus = true 
        })

        if (isVisible) {
            Popup(offset = IntOffset(0, 40),) {
                val focusRequester = remember { FocusRequester() }
                val interactionSource = remember { MutableInteractionSource() }
                val focused by interactionSource.collectIsFocusedAsState()

                LaunchedEffect(shouldFocus) {
                    if (shouldFocus) focusRequester.requestFocus()
                    shouldFocus = false
                }

                Box(
                    modifier = Modifier
                        .focusRequester(focusRequester)
                        .focusable(interactionSource = interactionSource)
                        .clickable { /* TODO */ }
                ) {
                    BasicText(if (focused) "focused" else "not focused")
                }
            }
        }
    }
I only added the
interactionSource
to show the focus state, it is not needed.
a
@Tobias Suchalla smart. need to check this approach out