What happens when I set a `Modifier.focusable()` e...
# compose
a
What happens when I set a
Modifier.focusable()
exactly? I was under the impression that I was telling compose that a composable can be focused, but it seems like using it multiple times on the same composable breaks focus requesters. Code in thread. My use case is: I am building a drag and drop modifier. I am moving focus to the item that has been picked. I was also adding
focusable()
on it but it seems like it is breaking the focus requesting for anyone else.
Here is some code:
Copy code
val requester = remember { FocusRequester() }
    Box(
        Modifier
            .size(90.dp)
            .background(Color.Red)
            .focusRequester(requester)
            .focusable()  // <----- 
            .onFocusChanged { state -> println("Has focused = ${state}") }
            .clickable {
                requester.requestFocus()
            }
    )
When i run the above code w/o the focusable, then clicking will grab focus. It prints:
Copy code
Has focused = Inactive //<- after launch
Has focused = Active   //<- after click
But, when i add the focusable() modifier, then clicking will grab focus and then mark is as inactive:
Copy code
Has focused = Inactive  // <- after launch
Has focused = Active   // <- after click
Has focused = Inactive //<- immediately shown after the above
Ok i think i figured it out. It's about the order of the Modifiers. This will cause the 'clickable()' to grab the focus. so this won't crash:
Copy code
Text(
        text = "Here is some text",
        modifier = Modifier
            .clickable { 
                fs.requestFocus() //<- requesting focus, focuses the clickable because it's the first 'focusable'
            }
            .focusRequester(fs)
            .onFocusChanged { focused ->
                if (focused.isFocused) {
                    error("IS FOCUSED")
                }
            }

    )
On the other hand if the focus clickable is after the on Focus changed this crashes:
Copy code
Text(
        text = "Here is some text",
        modifier = Modifier
            .onFocusChanged { focused ->
                if (focused.isFocused) {
                    error("IS FOCUSED")
                }
            }
            .clickable {
                fs.requestFocus() // <- requesting focus focuses the Text() because it's the first focusable()
            }
            .focusRequester(fs)

    )
now whether this is indeed how focusable work or not, I have no idea. it's an observation and i cant find info about this in the docs
l
For what you are talking about, we can basically consider
focusable
to be a
focusTarget()
- focusable adds lots of different logic on top, but purely for interacting with the focus system in this way it’s just a
focusTarget
under the hood.
I was under the impression that I was telling compose that a composable can be focused, but it seems like using it multiple times on the same composable breaks focus requesters.
When you add a
focusTarget()
modifier, you are adding a focus node in the tree. This is a node that can be focused, and receive information about focus events. So if you use
focusable
/
focusTarget
multiple times, you are just adding multiple distinct focusable nodes in the tree. This is because modifiers don’t ‘set’ properties on a layout or something, for example why if you do Modifier.padding(10.dp).padding(10.dp) this is actually 20.dp of padding, not 10.
For modifiers that interact with focus, such as
focusRequester
and
onFocusEvent
, they basically apply to the nearest child focus target in the hierarchy. So in your original example, the problem as you noticed is that when you add the new focusable modifier, you now basically have two independently focusable nodes on this box, and the focus requester only applies to one of them (the focusable), not the clickable
Ok i think i figured it out.
It’s about the order of the Modifiers.
This will cause the ‘clickable()’ to grab the focus. so this won’t crash:
On the other hand if the focus clickable is after the on Focus changed this crashes:
But I’m not sure I understand this part, and what the clickable ‘crashing’ signifies. It should be the other way around, the first one won’t request focus to the clickable, because the clickable is above the requester
The other complexity here is that clickable is only focusable in non-touch mode, so if you try and request focus manually while you are interacting with the touch screen (and not keyboard / mouse), then it won’t gain focus anyway
a
thanks for clarifying that each focus target is it's own thing. that's a huge thing missing from the docs, or it is too hidden. current focus docs lead to a broken mental model that causes a lot of wasted time on debugging stuff like this.
but focus makes sense now (after the debugging and your explanation)
l
Could you file a bug to track this? There's probably a lot of room to make this more clear
a
Sure thing
y
Focusable is used a lot on Wear. Specifically because it's how hardware events get directed to a particular component.
IMHO any code using LaunchedEffect
{ requester.requestFocus() }
is a bug. And it's a pretty common sample. Your control via clicking is fine.
Wear deals with this by using a
HierarchicalFocusCoordinator()
and calling requestFocus on your behalf, tied into lifecycle. So off screen composition doesn't steel it
106 Views