Flutter Focus Orchestration
This is part one of a series on the “lower-level” flutter components that tend to be abstracted away for you.
While you may never need this information, I had to figure most of this out from trial and error, as the official Flutter documentation does not go into this topic in as much depth as I wanted, so I think it makes sense to share what I found. The primary focus for me was to discover how to use Flutter without ever rendering a
WidgetsApp widget in the render tree. Why? That’s a topic for another day, but don’t worry, I will be writing about it in the near future.
To kick this off, there are several pieces you will need to be familiar with, several widgets that you will have to render in a certain order for focusing to work as you would expect it to:
Allows you to register keyboard shortcuts for your application. This is useful for our purposes in order to register a shortcut on the TAB key.
Creates a pairing between intents, which are fired off from shortcuts, and actions that certain other widgets may observe and act upon.
Manages a grouping of widgets and a certain focus policy which determines the order and mechanism for which widgets are focused.
Similar to a
Focuswidget, but also restricts the act of focusing to within its widget tree, acting as a scope for what is currently focusable.
The widget used for registering a node to be focusable. While this is the most important part of this article, it won’t work without the previously mentioned widgets.
Shortcuts widget allows you to register a pairing of keyboard shortcuts with intents. Intents are objects which signal to the
Actions component which action to actually fire off. To keep it simple for this article, let’s look at how to register a shortcut for the TAB key:
Notice the four new objects that were introduced in this code sample:
Registers a single shortcut activator using a keyboard key and optional modifiers (shift, control, meta, etc.)
Acts as an enum which includes static properties of keyboard keys.
An intent used to bind a shortcut to the
An intent used to bind a shortcut to the
Based on these objects and their definitions, you can see that if the user presses the TAB key, it will signal to the application an intent to focus the next focusable node. If the user presses the TAB key while also holding down the SHIFT key, it will signal an intent to focus the previous focusable node.
Now that you’ve registered two shortcuts to help manage focusing, and they signal intents to the application, we need a way to fire off actions based on these intents; this is exactly what the
Actions widget is for. It’s fairly straightforward, just like the
As you can see, the setup for actions is very similar to that of shortcuts, and maps the intents to actions that are then observed by other widgets. The two new objects introduced,
PreviousFocusAction are observed by the
Focus widgets. When you combine the code from shortcuts and actions, it looks as follows:
Once you’ve registered your shortcuts and actions, you need to tell your application what to do with the actions. In the case of focusing, you also want to define which order to focus the widgets in. This is what the
FocusTraversalGroup widget is useful for. There are three available policies which signal the order of focus to your application:
This policy focuses the widgets in the order in which they are created in the render hierarchy.
This policy will first focus widgets in the desired reading order, and then vertically down. It uses the
Directionalitywidget to determine the reading order. (You may need to render your own top-level
Directionalitywidget if you are not using any
This policy is the most customizable, as it focuses nodes in an explicit order defined by a
FocusTraversalOrderwidget and a
Now that you’ve chosen the proper order policy for your application, let’s take a look at an example using the
Once you’ve defined the mechanism and order for your focus, it’s important to define a scope for the widgets which are allowed to be focused. The
FocusScope widget restricts focus traversal to its own render tree. This can be highly useful for routing, for example. If you use a
WidgetsApp widget, a new
FocusScope widget is added to the tree every time a new route is pushed to the router. One important rule to note is that it requires the context provided by a
FocusTraversalGroup, so you may need to render a
Builder widget between them if you are doing all of this in a single widget:
Now that we’ve learned how to register shortcuts, and how to define the mechanism and order for focusing nodes, you can finally use the
Focus widget. There are plenty of articles and documentation that covers this widget in-depth, so I won’t go into it too deep. Instead, I think the most useful thing would be to show all of the code we’ve gone over in a single example:
Now you know how to orchestrate widget focus within your application at a “lower-level” by making use of shortcuts, actions, and the multiple different focus-related widgets. There are many ways to expand on this work to develop complex user experiences, so definitely feel free to take this work and run with it yourself! If you would like to see an interactive example of this code, I’ve written a quick one that you can run on DartPad (use the TAB and SHIFT + TAB to change the focused node):