Peter
08/25/2023, 9:34 AMTest()
re-composes? 🙂
fun Modifier.requestFocusSingleTime(): Modifier = composed {
val requester = remember { FocusRequester() }
LaunchedEffect(Unit) {
requester.requestFocus()
}
focusRequester(requester)
}
@Composable
fun Test() {
TextField(modifier = Modifier.requestFocusSingleTime())
}
Peter
08/25/2023, 11:22 AMvide
08/25/2023, 2:36 PMLaunchedEffect
might randomly execute before the focus requester is registered in the tree.vide
08/25/2023, 2:37 PMjava.lang.IllegalStateException:
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in
response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
vide
08/25/2023, 2:39 PMSideEffect
runs after the composition so you won't get any nasty race conditions like that, but then you will have to keep track of whether you already ran it or not.vide
08/25/2023, 2:42 PMthis.coroutineContext.job.invokeOnCompletion {}
with LaunchedEffect
vide
08/25/2023, 2:42 PMvide
08/25/2023, 2:42 PMPeter
08/25/2023, 2:44 PMfocusRequester(requester).also {
LaunchedEffect(){...}
}
vide
08/25/2023, 3:21 PMZach Klippenstein (he/him) [MOD]
08/25/2023, 8:09 PMcomposed
, which is terrible for performance. You can make a Modifier.Node
implement FocusRequesterModifierNode
, then either call requestFocus
in onAttach
or, if that’s too early in the modifier lifecycle (I’m not sure if it is), a coroutine launched from it.vide
08/25/2023, 8:22 PMcomposed
modifiers for some time to use the Modifier.Node api... Iirc there's no migration guide yet?Zach Klippenstein (he/him) [MOD]
08/25/2023, 8:25 PMvide
08/25/2023, 8:29 PMPeter
08/28/2023, 7:37 AMonAttach()
looks like it's too early in the lifecycle, since it throws "that exception" above. I've also tried sideEffect()
but no luck. @Zach Klippenstein (he/him) [MOD] can you please give me an example, how would you launch a coroutine 🙏
Also, I'm wondering, if there's any way to avoid having FocusRequester
as a dependency somehow.
fun Modifier.requestFocusOnce(requester: FocusRequester): Modifier =
this then RequestFocusElement(requester)
private class RequestFocusNode(
var focusRequester: FocusRequester
) : Modifier.Node() {
@OptIn(ExperimentalComposeUiApi::class)
override fun onAttach() {
super.onAttach()
sideEffect {
focusRequester.requestFocus()
}
}
}
private data class RequestFocusElement(
private val focusRequester: FocusRequester
) : ModifierNodeElement<RequestFocusNode>() {
override fun create(): RequestFocusNode =
RequestFocusNode(focusRequester)
override fun update(node: RequestFocusNode) {
node.focusRequester = focusRequester
}
}
Albert Chang
08/28/2023, 8:32 AMcoroutineScope.launch {}
in onAttach
.
Note that coroutineScope
is available since compose 1.5.0.Peter
08/28/2023, 8:33 AMFocusRequester is not initialized
was still thrownefemoney
08/28/2023, 9:02 AM.focusRequester(focusRequester)
)efemoney
08/28/2023, 9:04 AMFocusRequesterModifierNode
, that is a known node by the systemPeter
08/28/2023, 9:05 AMefemoney
08/28/2023, 9:08 AMIf this happens this is a bug from Compose runtime. LaunchedEffect is still an effect, it will not execute concurrently with composition (nor before).might randomly execute before the focus requester is registered in the tree.LaunchedEffect
Albert Chang
08/28/2023, 9:16 AMfun Modifier.requestFocusOnce(): Modifier =
this then RequestFocusElement
private data object RequestFocusElement : ModifierNodeElement<RequestFocusNode>() {
override fun create(): RequestFocusNode = RequestFocusNode()
override fun update(node: RequestFocusNode) = Unit
}
private class RequestFocusNode : FocusRequesterModifierNode, Modifier.Node() {
override fun onAttach() {
requestFocus()
}
}
Peter
08/28/2023, 9:39 AMoverride fun onAttach() {
coroutineScope.launch {
requestFocus()
}
}
java.lang.IllegalStateException: Check failed.
at androidx.compose.ui.focus.FocusTargetModifierNode.fetchFocusProperties$ui_release(FocusTargetModifierNode.kt:210)
at androidx.compose.ui.focus.FocusRequesterModifierNodeKt.requestFocus(FocusRequesterModifierNode.kt:41)
at androidx.compose.ui.Modifier$Node.attach$ui_release(Modifier.kt:248)
at androidx.compose.ui.node.NodeChain.attach(NodeChain.kt:271)
at androidx.compose.ui.node.LayoutNode.attach$ui_release(LayoutNode.kt:435)
at androidx.compose.ui.node.LayoutNode.attach$ui_release(LayoutNode.kt:437)
at androidx.compose.ui.node.LayoutNode.attach$ui_release(LayoutNode.kt:437)
at androidx.compose.ui.node.LayoutNode.attach$ui_release(LayoutNode.kt:437)
at androidx.compose.ui.node.LayoutNode.attach$ui_release(LayoutNode.kt:437)
at androidx.compose.ui.node.LayoutNode.insertAt$ui_release(LayoutNode.kt:299)
at androidx.compose.ui.node.UiApplier.insertBottomUp(UiApplier.android.kt:31)
at androidx.compose.ui.node.UiApplier.insertBottomUp(UiApplier.android.kt:21)
at androidx.compose.runtime.ComposerImpl$createNode$3.invoke(Composer.kt:1622)
at androidx.compose.runtime.ComposerImpl$createNode$3.invoke(Composer.kt:1617)
vide
08/28/2023, 10:03 AMSideEffect
runs after the composition:
Schedulehttps://developer.android.com/reference/kotlin/androidx/compose/runtime/package-summary#SideEffect(kotlin.Function0)to run when the current composition completes successfully and applies changes.effect
Stylianos Gakis
08/28/2023, 10:08 AMSideEffect
and DisposableEffect
run directly after the first successful composition, while LaunchedEffect
runs after the first composition but also due to coroutine scheduling 1 frame later too?
Which is a reason why for some things if you want them to be keyed or run just once (so gotta go with LaunchedEffect or DisposableEffect) you go with DisposableEffect(Unit) { doThing; onDispose{} }
so that you get the faster action and an empty onDispose block?
Like here for exampleAlbert Chang
08/28/2023, 10:08 AMStylianos Gakis
08/28/2023, 10:09 AMefemoney
08/28/2023, 10:11 AMvide
08/28/2023, 10:13 AMvide
08/28/2023, 10:14 AMcomposed
and Modifier.Node
versions?efemoney
08/28/2023, 10:14 AMefemoney
08/28/2023, 10:16 AMefemoney
08/28/2023, 10:18 AMLaunchedEffect running before they are registeredIf you still have a reproducer I would suggest submitting an issue 🙏🏾
vide
08/28/2023, 10:20 AMval f = remember { FocusRequester() }
Box(Modifier.focusRequester(f).focusTarget())
LaunchedEffect(Unit) { f.requestFocus() }
efemoney
08/28/2023, 10:30 AMvide
08/28/2023, 10:32 AMefemoney
08/28/2023, 10:33 AMefemoney
08/28/2023, 10:33 AMvide
08/28/2023, 10:34 AMefemoney
08/28/2023, 10:34 AMfocusTarget
, that API i am not sure about as I have not used it. Maybe thats your issue?efemoney
08/28/2023, 10:34 AMWhat compose version are you using?That code was added in compose 1.3 and has seen both 1.4 & 1.5 upgrades
vide
08/28/2023, 10:35 AMclickable
or focusable
, they both contain it internallyefemoney
08/28/2023, 10:36 AMvide
08/28/2023, 10:44 AMvide
08/28/2023, 10:49 AMLaunchedEffect(Unit) { f.requestFocus() }
after allPeter
08/28/2023, 10:54 AM