Flutter - Handle key event only if there is no text entry event

Viewed 482

I'm trying to implement some shortcuts on a desktop app. I've been looking into these links:

I would like to do an action when the user presses on the key a (for example).

class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

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

class _TestState extends State<Test> {
  int count = 0;

  KeyEventResult onKey(FocusNode node, RawKeyEvent event) {
    if (event is RawKeyDownEvent && event.logicalKey == LogicalKeyboardKey.keyA) {
      setState(() {
        count++;
      });
      return KeyEventResult.handled;
    }
    return KeyEventResult.ignored;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Focus(
          onKey: onKey,
          child: Column(
            children: [
              Text('count: $count'),
              const TextField(),
              TextButton(
                child: Text('button'),
                onPressed: () {},
              ),
            ],
          ),
        ),
      ),
    );
  }
}

When the user clicks on a, the count increments.


The issue with this code is the user being unable to type a in the text field anymore because it is being handled by the focus node. The first link states:

Key events start at the focus node with primary focus. If that node doesn’t return KeyEventResult.handled from its onKey handler, then its parent focus node is given the event. If the parent doesn’t handle it, it goes to its parent, and so on, until it reaches the root of the focus tree.

and

Focus key events are processed before text entry events, so handling a key event when the focus widget surrounds a text field prevents that key from being entered into the text field.


I would like my Focus widget to handle the key event only if the subtree didn't handle it itself including TextFields (and therefore text entry events).

I tried to always return KeyEventResult.ignored in the onKey method, but the OS triggers a sound meaning there is no action available every time the user clicks on a.


Is there a way to implement what I am trying to do? If yes, how?

1 Answers

You could wrap your text field in a focus that returns KeyEventResult.skipRemainingHandlers in onKeyEvent. This will prevent it affecting the other handlers while the text field is focused. Although this is inelegant and I'd love a correction with a better solution.

   const unhandledKeys = [
  LogicalKeyboardKey.delete,
  LogicalKeyboardKey.backspace,
  LogicalKeyboardKey.arrowUp,
  LogicalKeyboardKey.arrowDown,
  LogicalKeyboardKey.arrowLeft,
  LogicalKeyboardKey.arrowRight
];

   final focusNode = useFocusNode(onKeyEvent: (_a, _b) {
      if (unhandledKeys.contains(event.logicalKey)) {
        return KeyEventResult.ignored;
      }
      return KeyEventResult.skipRemainingHandlers;
    });

    return Focus(
      focusNode: focusNode,
      child: TextField()
    );
Related