'Flutter RawKeyboardListener listening twice?
What I am trying to achieve is when viewing this widget, the RawKeyboardListener
starts listening straight away when the TextField
is not selected/in focus. It runs the HandleKey function
to deal with what I want to do with the keyCode
.
The issue I am having is when running the app for the first time, the handleKey function
seems to be running twice. So in the example below it would print why does this run twice $_keyCode
TWICE when I only enter 1 key. I think it listens to keyUp AND keyDown. The result I want is for it to only run once...
However, the code works fine as well when I select the TextField and do a regular submit with the emulator keyboard.
I am struggling to understand why it only has a problem after interacting with the TextField. I feel like it needs a Future
or await
somewhere? but I have no idea.
Please help.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';
class KeyboardListener extends StatefulWidget {
KeyboardListener();
@override
_RawKeyboardListenerState createState() => new _RawKeyboardListenerState();
}
class _RawKeyboardListenerState extends State<KeyboardListener> {
TextEditingController _controller = new TextEditingController();
FocusNode _textNode = new FocusNode();
@override
initState() {
super.initState();
}
//Handle when submitting
void _handleSubmitted(String finalinput) {
setState(() {
SystemChannels.textInput.invokeMethod('TextInput.hide'); //hide keyboard again
_controller.clear();
});
}
handleKey(RawKeyEventDataAndroid key) {
String _keyCode;
_keyCode = key.keyCode.toString(); //keycode of key event (66 is return)
print("why does this run twice $_keyCode");
}
_buildTextComposer() {
TextField _textField = new TextField(
controller: _controller,
onSubmitted: _handleSubmitted,
);
FocusScope.of(context).requestFocus(_textNode);
return new RawKeyboardListener(
focusNode: _textNode,
onKey: (key) => handleKey(key.data),
child: _textField
);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Search Item")),
body: _buildTextComposer(),
);
}
}
Solution 1:[1]
Your callback is getting called for both keydown and keyup events with instances of following classes:
- RawKeyDownEvent
- RawKeyUpEvent
You can pass the whole object to handleKey, and filter based on runtime type of object. for example
handleKey(RawKeyEvent key) {
print("Event runtimeType is ${key.runtimeType}");
if(key.runtimeType.toString() == 'RawKeyDownEvent'){
RawKeyEventDataAndroid data = key.data as RawKeyEventDataAndroid;
String _keyCode;
_keyCode = data.keyCode.toString(); //keycode of key event (66 is return)
print("why does this run twice $_keyCode");
}
}
_buildTextComposer() {
TextField _textField = new TextField(
controller: _controller,
onSubmitted: _handleSubmitted,
);
FocusScope.of(context).requestFocus(_textNode);
return new RawKeyboardListener(
focusNode: _textNode,
onKey: handleKey,
child: _textField
);
}
If this still does not help, check actual runtimeTypes logged from handleKey method, and filter by those.
Solution 2:[2]
The onKey
callback is triggered for both key down and key up events. That's why it appears to be called twice for a single key press.
When handling the events, I prefer using is
rather than accessing the runtime type:
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent) {
// handle key down
} else if (event is RawKeyUpEvent) {
// handle key up
}
},
Solution 3:[3]
You are right. RawKeyboardListener
listens on raw keyboard events. Which means it returns down and up (or how the naming convention is on touchscreens). Knowing that you could simply create a if-statement and just get through the event once:
bool _tempKeyPressedOnce = false;
if (!_tempKeyPressedOnce) {
// do stuff
_tempKeyPressedOnce = true;
}
Solution 4:[4]
Eyo, played around with the values variables and noticed that if you use the iskeypressed on the second time round it's false. Id hazard a guess that normally its either detecting the press and the release.
so
RawKeyboardListener(
focusNode: FocusNode(),
autofocus: true,
//includeSemantics: true,
onKey: (value){
print("1) ${value.data}");
print("2) ${value.character.toString()}");
print("3) ${value.toString()}");
print("4) ${value.physicalKey.debugName}");
print("5) ${value.logicalKey.keyId}");
print("6) ${value.isKeyPressed(LogicalKeyboardKey.enter)}");
setState(() {
///add string to list and clear text or not ?
value.logicalKey == LogicalKeyboardKey.enter ? print("YES A") : 0;
value.isKeyPressed(LogicalKeyboardKey.enter) ? print("YES B") : 0;
}
);
},
Results in a
flutter: 1) Instance of 'RawKeyEventDataWindows'
flutter: 2)
flutter: 3) RawKeyDownEvent#13d45(logicalKey: LogicalKeyboardKey#70028(keyId: "0x100070028", keyLabel: "Enter", debugName: "Enter"), physicalKey: PhysicalKeyboardKey#70028(usbHidUsage: "0x00070028", debugName: "Enter"))
flutter: 4) Enter
flutter: 5) 4295426088
flutter: 6) true
flutter: YES A
flutter: YES B
flutter: NEXT SET
flutter: ***********************************
flutter: 1) Instance of 'RawKeyEventDataWindows'
flutter: 2) null
flutter: 3) RawKeyUpEvent#9dc07(logicalKey: LogicalKeyboardKey#70028(keyId: "0x100070028", keyLabel: "Enter", debugName: "Enter"), physicalKey: PhysicalKeyboardKey#70028(usbHidUsage: "0x00070028", debugName: "Enter"))
flutter: 4) Enter
flutter: 5) 4295426088
flutter: 6) false
flutter: YES A
flutter: NEXT SET
flutter: ***********************************
Solution 5:[5]
Using isKeyPressed
worked for me.
My Working code
RawKeyboardListener(
focusNode: _focusNodeKeyboard,
onKey: (event) {
if (event.isKeyPressed(LogicalKeyboardKey.backspace)) {
print('Backspace Pressed'); // Printed Once
}
},
)
Older Version
RawKeyboardListener(
focusNode: _focusNodeKeyboard,
onKey: (event) {
if (event.logicalKey == LogicalKeyboardKey.backspace) {
print('Backspace Pressed'); // Printed Twice
}
},
)
Solution 6:[6]
This is how you can get it to work:
RawKeyboardListener(
focusNode: FocusNode(),
onKey: (event) {
// Only taking key down event into consideration
if (event.runtimeType == RawKeyDownEvent) {
bool shiftPressed = event.isShiftPressed; // true: if shift key is pressed
}
},
child: TextField(),
)
Solution 7:[7]
RawKeyboardListener
Widget A widget that calls a callback whenever the user presses or releases a key on a keyboard.
The following code of how to implement RawKeyboardListener
First handles the key events from the RawKeyboardListener
and update the _message
void _handleKeyEvent(RawKeyEvent event) {
setState(() {
if (event.logicalKey == LogicalKeyboardKey.keyQ) {
_message = 'Pressed the "Q" key!';
} else {
if (kReleaseMode) {
_message = 'Not a Q: Key label is "${event.logicalKey.keyLabel ?? '<none>'}"';
} else {
// This will only print useful information in debug mode.
_message = 'Not a Q: Pressed ${event.logicalKey.debugName}';
}
}
});
}
After use that method like this
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return Container(
color: Colors.white,
alignment: Alignment.center,
child: DefaultTextStyle(
style: textTheme.display1,
child: RawKeyboardListener(
focusNode: _focusNode,
onKey: _handleKeyEvent,
child: AnimatedBuilder(
animation: _focusNode,
builder: (BuildContext context, Widget child) {
if (!_focusNode.hasFocus) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(_focusNode);
},
child: const Text('Tap to focus'),
);
}
return Text(_message ?? 'Press a key');
},
),
),
),
);
}
Solution 8:[8]
I use this function to write values from the keyboard
handleKey(RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.physicalKey == PhysicalKeyboardKey.enter) {
log('ENTER');
_text = '';
} else {
log('Event data keyLabel ${event.data.keyLabel}');
_text += event.data.keyLabel;
}
log('text: $_text');
}
}
It does not duplicate writing values from the keyboard
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Ganapat |
Solution 2 | |
Solution 3 | Bostrot |
Solution 4 | lolbardsnin |
Solution 5 | ASAD HAMEED |
Solution 6 | CopsOnRoad |
Solution 7 | Paresh Mangukiya |
Solution 8 | Nikolay |