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.

See the runnable code example on DartPad at the bottom of this article.

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:

  • Actions
    Creates a pairing between intents, which are fired off from shortcuts, and actions that certain other widgets may observe and act upon.
  • FocusTraversalGroup
    Manages a grouping of widgets and a certain focus policy which determines the order and mechanism for which widgets are focused.
  • FocusScope
    Similar to a Focus widget, but also restricts the act of focusing to within its widget tree, acting as a scope for what is currently focusable.
  • Focus
    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

The 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:

  • LogicalKeyboardKey
    Acts as an enum which includes static properties of keyboard keys.
  • NextFocusIntent
    An intent used to bind a shortcut to the NextFocusAction (see below).
  • PreviousFocusIntent
    An intent used to bind a shortcut to the PreviousFocusAction (see below).

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.

Actions

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 Shortcuts widget:

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, NextFocusAction & PreviousFocusAction are observed by the FocusTraversalGroup, FocusScope, and Focus widgets. When you combine the code from shortcuts and actions, it looks as follows:

FocusTraversalGroup

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:

Now that you’ve chosen the proper order policy for your application, let’s take a look at an example using the WidgetOrderTraversalPolicy:

FocusScope

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:

Focus

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):

Happy developing!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store