Alexander Maryanovsky
03/25/2022, 9:38 PMModifier
that, when the element it modifies is “selected” (in a sense I define), it automatically makes it focused.
fun Modifier.focusedWhenSelected(): Modifier = this.then(FocusedWhenSelected)
object FocusedWhenSelected: Modifier.Element{
fun implementingModifier(modifier: Modifier, isSelected: Boolean): Modifier{
val hasFocusedWhenSelected = modifier.any { it == FocusedWhenSelected }
if (!hasFocusedWhenSelected)
return Modifier
return Modifier.composed {
val focusRequester = remember { FocusRequester() }
if (isSelected) {
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
Modifier
.focusRequester(focusRequester)
.focusable()
}
}
}
and then, in the code that actually defines isSelected
, I do:
Box(
modifier = FocusedWhenSelected.implementingModifier(modifier, isSelected)
){
...
}
Does this look right? I’m somewhat worried that I’m creating a new Modifier.composed
every time.Zach Klippenstein (he/him) [MOD]
03/26/2022, 8:57 PMUnit
to launched effect, pass its dependencies. In this case that’s focusRequester
.Zach Klippenstein (he/him) [MOD]
03/26/2022, 8:59 PMZach Klippenstein (he/him) [MOD]
03/26/2022, 9:00 PMI’m somewhat worried that I’m creating a newWell that’s howevery time.Modifier.composed
composed
works. I think there are other issues with this approach that should be addressed before worrying about that.Zach Klippenstein (he/him) [MOD]
03/26/2022, 9:02 PMAlexander Maryanovsky
03/26/2022, 9:31 PMAlexander Maryanovsky
03/26/2022, 9:34 PMfocusRequester
as the key to LaunchedEffect
- if focusRequester
changes, it’s a new composition anyway, and the effect will be launched again. Am I wrong?
Not sure what you mean about two modifiers - there is only one modifier (FocusedWhenSelected
). I’m examining the chain to see whether it’s present, and if it is, I “apply” it. It just happens that applying it involves using some other modifiers.Alexander Maryanovsky
03/26/2022, 9:35 PMfocusRequester().focusable()
modifier from the LaunchedEffect
requesting focus because it needs the focusRequester
Alexander Maryanovsky
03/26/2022, 9:47 PMFocusedWhenSelected
is just a marker - it doesn’t “do” anything by itself, it just tells the code that actually knows about whether the element is “selected” that it should give it focus. implementingModifier
is inside FocusedWhenSelected
just for scoping. It could’ve been a top-level function just as well.Zach Klippenstein (he/him) [MOD]
04/11/2022, 6:26 PMNo, no, “isSelected” is the source of truth.It’s one of two. The focus system maintains its own private state about what is currently focused, which is the second source of truth that you can’t avoid if you want to interact with focus at all. The reason Compose doesn’t let you hoist focus state yourself is probably because it’s pretty complex and can come from the system, although I’m not 100% sure it’s impossible. That said, since you can’t get rid of the focus state as a source of truth, the only source you can eliminate is your own.
I don’t think in this case it’s important to passThat’s true, for now, but code has a way of growing and not following best practices like this can introduce little bugs later.as the key tofocusRequester
- ifLaunchedEffect
changes, it’s a new composition anyway, and the effect will be launched again. Am I wrong?focusRequester
LaunchedEffect
requires a parameter anyway – if you have to pass something in, why not pass in the thing that it’s designed to take?
Not sure what you mean about two modifiers - there is only one modifier (There’s also the composed modifier you’re returning from).FocusedWhenSelected
implementingModifier
. My question was why do you need that indirection? Why can’t you just have your consumers apply implementingModifier
when they want something to be focused-when-selected? This:
Box(
modifier = FocusedWhenSelected.implementingModifier(modifier, isSelected)
)
could just be this (although probably you’d want a different name):
Box(
modifier = Modifier.implementingModifier(modifier, isSelected)
)
That said, if the answer is because the thing making the decision about whether to apply FocusedWhenSelected
is in an entirely different part of the code than the one applying implementingModifier
, then you’d be better off using ModifierLocals for this instead of trying to look for the presence of a specific modifier in the chain yourself.Alexander Maryanovsky
04/11/2022, 7:51 PMfocusedWhenSelected
modifier is not the same code that decides whether the element is “selected”. I’ll look into ModifierLocals. It sounds promising.
To the first part - I probably haven’t explained myself well. isSelected
is completely my own thing. It does not at all have to be related to focus. The modifier I’m trying to implement here is what creates this relation. The relation being that when an element is deemed “selected”, it should also automatically get focus.Alexander Maryanovsky
05/20/2022, 11:46 AMModifierLocal
worked wonderfully:
/**
* When the given [ModifierLocal]'s value is `true`, requests focus for the node to which this modifier is applied.
*/
@Composable
fun Modifier.requestFocusWhen(modifierLocal: ModifierLocal<Boolean>, focusRequester: FocusRequester) = composed {
var requestFocus by remember { mutableStateOf(false) }
if (requestFocus){
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
this
.focusable()
.modifierLocalConsumer {
requestFocus = modifierLocal.current
}
}
/**
* Specifies the "selected" state of the node.
*/
private val SelectedLocalModifier: ProvidableModifierLocal<Boolean> = modifierLocalOf { false }
/**
* Sets the "selected" state of the node to which this modifier is applied.
* Together with [requestFocusWhenSelected], this can be used to automatically transfer focus to the currently selected item.
*/
fun Modifier.markSelected(isSelected: Boolean) = this.modifierLocalProvider(SelectedLocalModifier){ isSelected }
/**
* Requests focus using the given [FocusRequester] when the node, or one of its parents is marked as selected via
* [markSelected].
*/
@Composable
fun Modifier.requestFocusWhenSelected(focusRequester: FocusRequester) = requestFocusWhen(SelectedLocalModifier, focusRequester)