The original address: wilsonwilson. Dev/the rid_device_info_keyboard – sh…

Wilsonwilson.dev /

Release date: February 1, 2021

A few weeks ago, I shared a Pomodoro app built entirely with Flutter. Under normal circumstances, this would be boring. But this application is intended as an example of a basic desktop application built with Flutter. It has everything a typical desktop application needs, such as window management, keyboard shortcuts, and even access to the menu bar.

Viewing a Resource Library

Today I want to write about one of these features, as you might have guessed from the title, the keyboard shortcuts in Flutter! If you want to implement more specific desktop features in Flutter, such as levitation, please sign up to my mailing list so you don’t miss out!

In this tutorial, we will recreate the default counter application, but use keyboard bindings to increase and decrease values when we press the shortcut key.

Wrong way

You’ve probably seen the RawKeyboardListener widget (which has been popular lately), which allows you to listen for “up” and “down” events on your keyboard.

@override
 Widget build(BuildContext context) {
   return RawKeyboardListener(
       autofocus: true,
       onKey: (event) {
         if (event.runtimeType == RawKeyDownEvent) {
          if (event.physicalKey == PhysicalKeyboardKey.keyX) {
             runSomeCode();
           }
         }
       },
       focusNode: FocusNode(),
       child: child,
    );
 }
Copy the code

You can implement keyboard shortcuts with this gadget. But I don’t recommend it for a number of reasons.

First of all, you can’t use multiple keys.

Not only will you have to filter events under buttons, but you won’t be able to integrate them with other desktop-specific plug-ins (mostly due to the lack of a unified implementation).

For example, if you want to use the menubar plugin with your shortcut keys, you will have to use LogicalKeySet, and RawKeyboardListener is not based on that.

In addition, you won’t be able to access many native features, such as key duplication and invalid key notification (sound).

In other words, using this widget to handle keyboard shortcuts is a bad idea 😖.

Don’t do this!

This widget is a better way to get input from the keyboard without having to use TextField, which is also very useful 🚀.

You can read more about RawKeyboardListener in this article or documentation on Medium.

A better way

A more useful widget is the FocusableActionDetector, which comes with a very simple API to listen for keyboard shortcuts.

It accepts a mapping between a shortcut (LogicalKeySet: Intent) and an Action (Intent: Action).

Intention and action

The primary purpose of an Intent is to describe an event/action. Take a browser application, for example, whose keyboard shortcut can create a new TAB. Such an Intent should look like this.

class NewTabIntent extends Intent {}
Copy the code

That’s all you need to know. So, in our counter example, where we want to increment and decrement values via keyboard shortcuts, we need the following Intent.

class IncrementIntent extends Intent {}
class DecrementIntent extends Intent {}
Copy the code

An Action is used to do something after receiving an Intent. Once the action is “called”, we execute some code! In our new TAB example, the intended action would look like this.

CallbackAction(onInvoke: (_) => createNewTab())
Copy the code

Different scenes have different types of actions and intentions. They are mostly descriptive, so the examples above are probably all you need to create an application. You can see more types of actions and intents in the source code.

Let’s create the counter application!

Create a new FLUTTER project for the Web/desktop. We won’t change much of the original source code. If you want to use Dartpad, the code here (but without the comment 🙃).

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page')); }}class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

Since we will also decrement our counter, we should probably create a method that decrement the counter.

   void _decrementCounter() {
     setState(() {
       _counter--;
     });
   }
Copy the code

That’s great! All the code for our counter is now complete! Let’s start creating our shortcut.

First, we need to create our intents and shortcut keysets. Add two top-level logical keysets and two Intents for increment and decrement counters.

final incrementKeySet = LogicalKeySet(
   LogicalKeyboardKey.meta, // Replace with control on Windows
   LogicalKeyboardKey.arrowUp,
 );
 final decrementKeySet = LogicalKeySet(
   LogicalKeyboardKey.meta, // Replace with control on Windows
   LogicalKeyboardKey.arrowDown,
 );
 class IncrementIntent extends Intent {}
 class DecrementIntent extends Intent {}
Copy the code

Notice how we use logicalKeyboardkey.meta. This represents the CMD key on the macOS, on Windows, use LogicalKeyboardKey. Control instead. On Windows, use LogicalKeyboardKey. Replace the control.

To see a list of all possible logical keyboard keys in Flutter, check the documentation.

Great! Now we are ready to create our logical keyboard keys. Now we can go ahead and create our Widget, which will listen for our shortcuts and react to them. We call this widget CounterShortcuts. It requires a child widget and two callbacks…… One in response to incremental events and one in response to decrement events.

class CounterShortcuts extends StatelessWidget {
 const CounterShortcuts({
     Key key,
     @required this.child,
     @required this.onIncrementDetected,
     @required this.onDecrementDetected,
   }) : super(key: key);
 final Widget child;
   final VoidCallback onIncrementDetected;
   finalVoidCallback onDecrementDetected; .Copy the code

Now we can finally build our widget with our FocusableActionDetector🎉.

I’ll post the code and explain it later.

   @override
   Widget build(BuildContext context) {
     return FocusableActionDetector(
       autofocus: true, shortcuts: { incrementKeySet: IncrementIntent(), decrementKeySet: DecrementIntent(), }, actions: { IncrementIntent: CallbackAction(onInvoke: (e) => onIncrementDetected? .call()), DecrementIntent: CallbackAction(onInvoke: (e) => onDecrementDetected? .call()), }, child: child, ); }Copy the code

First, we need to give the FocusableActionDetector a child node. In this case, it will be a counter widget.

Next, we need to think about how to manage its focus. In a larger application with many text fields and other widgets that might need to be focused, it is best to provide a focus node for the FocusableActionDetector and manage it ourselves. But since there is nothing else in our application that can request focus, we can simply set its autofocus parameter to true.

Now we need to provide it with our shortcuts and actions. In the shortcut, we pass in a Map of type Map<LogicalKeySet, Intent>. So, for our incremental keyset, we pass in incrementIntents, and for our decrement keyset, we provide DecrementIntents.

Our actions take a Map of Type Map<Type, Action>. So for our IncrementIntent, we provide a CallbackAction that will call our onIncrementDetected callback when it is called, and also for DecrementIntent.

The last thing we need to do is wrap our counters with our CounterShortcuts Widget. When onIncrementDetected is called, we increment the counter and do the same for onIncrementDetected.

   @override
   Widget build(BuildContext context) {
     return CounterShortcuts(
       onIncrementDetected: _incrementCounter,
       onDecrementDetected: _decrementCounter,
       child: Scaffold(
         appBar: AppBar(
           title: Text(widget.title),
         ),
...
Copy the code

And then that’s it! We now have a fully functional counter application that responds to the shortcut keys 🚀.

(For demonstration purposes, I’ve added a snack bar that shows which keys were pressed.)

If you want to learn how to use the shortcut keys of the menu bar, join my mailing list! A tutorial is coming soon 😃.


Translation via www.DeepL.com/Translator (free version)