One Off the Slack: Issues with Dependent Modifiers
Chris Stratton asked:
focusable(...)
does not seem to be working as advertised? I’m trying to make aBox
(orSurface
- I’m not fussy :grin:) be focusable so have modified it to:
- be
.focusable(true)
- have a
focusOrder(myFocusRequester)
Documentation suggests this is all we need for a custom composable to be focused, and yet, when I call
requestFocus()
on myBox
’s focus requester, the previously focused component (a regularTextField
) does not appear to ‘let go’ of it’s focus, also TalkBack does not shift it’s highlight to theBox
component. What else do I need to do to effect a full transfer of focus over to my custom component?
Ryan Nett responded with the solution:
Make sure you’re putting the
focusOrder
first, that’s gotten me a couple of times.
In other words, Chris had something akin to:
Box(modifier = Modifier.focusable(true).focusOrder(myFocusRequester))
…and the solution is to invert the order of those modifiers:
Box(modifier = Modifier.focusOrder(myFocusRequester).focusable(true))
Those two modifiers do not need to be used together, but when they are, there
is a specific order in which they need to be used. Modifiers do not necessarily
know about other modifiers, and so it may be difficult for focusOrder()
to check to
see that focusable()
was called previously.
So, the API is technically correct, yet it represents a footgun.
Chris was happy to find the solution but unhappy about how it all went down:
This feels like such an obvious gotcha for devs trying to use these two modifiers in combination; it really would be quite easy to include in the API comments. Not proud to admit this probably cost me a day 😞
As you design your own modifiers for your own composables, be careful about creating modifiers that have a strict dependency on their order of application.
One solution is to maintain the independent modifiers but have parameter options
that allow their effects to be combined in a single modifier. For example, in this
case, focusable()
could take an optional FocusRequester
parameter, eliminating the need
for a separate focusOrder()
call, and allowing focusable()
to ensure that everything
is done properly.
If that is impractical, try to find ways to at least help developers identify footgun scenarios:
-
Lint rules
-
API documentation, as Chris suggests
Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!