Actions, Intents, and Shortcuts in Flutter

Om Londhe
3 min readNov 26, 2021

--

We are developing an application that works on all the great platforms out there, but still, there’s something missing. What if our users are using your application on the desktop like a native application and press Ctrl/Cmnd + A for selecting everything and nothing happens, users will say that the application is trash as it does not allow you to use shortcuts. But flutter cannot leave us alone and let our application be affected by such things. Thus they provided us with this new topic.

See the above image, it’s the straightforward architecture of this new system. We can see that we press keys and the key event is fired. For the given key bindings, the intent is fulfilled and action is invoked on the user’s demand.

You may be thinking that why should we use Actions and Intents separated? This is not compulsory but it will be great if this is followed as a separation of concerns between where the key mapping definitions are (often at a high level), and where the action definitions are (often at a low level), and because it is important to be able to have a single key combination map to an intended operation in an app and have it adapt automatically to whichever action fulfills that intended operation for the focused context.

Callbacks are also one option, but we shouldn’t use them too, because it is useful for actions to decide whether they are enabled by implementing isEnabled.

We talked a lot about Actions and Intents, let’s go ahead and see the read use-case. These things are used in defining the shortcuts, see the code below-

@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyA):
SelectAllIntent(),
}, // defining shortcut keys
child: Actions(
dispatcher: LoggingActionDispatcher(),
actions: <Type, Action<Intent>>{
SelectAllIntent: SelectAllAction(model),
},
child: Builder(
builder: (BuildContext context) => TextButton(
child: const Text('SELECT ALL'),
onPressed: Actions.handler<SelectAllIntent>(
context,
SelectAllIntent(),
),
),
),
),
);
}

The shortcut manager, a longer-lived object than the Shortcuts widget, passes on key events when it receives them. It contains the logic for deciding how to handle the keys, the logic for walking up the tree to find other shortcut mappings, and maintains a map of key combinations to intents. Use the following code to log each key that a Shortcuts widget handled.

class LoggingShortcutManager extends ShortcutManager {
@override
KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) {
final KeyEventResult result = super.handleKeypress(context, event);
if (result == KeyEventResult.handled) {
print('Handled shortcut $event in $context');
}
return result;
}
}

We will make our hands dirty with this tomorrow!

Thank you 🙏

--

--