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 a
Surface- I’m not fussy :grin:) be focusable so have modified it to:
- have a
Documentation suggests this is all we need for a custom composable to be focused, and yet, when I call
Box’s focus requester, the previously focused component (a regular
TextField) does not appear to ‘let go’ of it’s focus, also TalkBack does not shift it’s highlight to the
Boxcomponent. 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
focusOrderfirst, 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
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
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:
API documentation, as Chris suggests