Many applications can delete rows by pressing Delete button while they are selected. How can it be implemented in Flutter? We need KeyboardListener to handle the keyboard event.
How to detect a key press event on CheckBox
To handle the keyboard event, we need KeyboardListener
. There is a similar Widget called RawKeyboardListener
. Since it’s legacy, KeyboardListener
should be used if possible.
The RawKeyboardListener is different from KeyboardListener in that RawKeyboardListener uses the legacy RawKeyboard API. Use KeyboardListener if possible.
https://api.flutter.dev/flutter/widgets/KeyboardListener-class.html
The basic usage of KeyboardListener
is the following.
final focusNode = FocusNode();
KeyboardListener(
focusNode: focusNode,
onKeyEvent: (event) {
debugPrint("logical: ${event.logicalKey.keyLabel}\n" + "physical: ${event.physicalKey.debugName}");
},
child: child,
),
Define FocusNode
and assign it to the listener. Then, put the desired child widget where we want to detect a key event. When the child widget has a Focus, a key event is triggered. The event can be defined in onKeyEvent
.
import 'common.dart';
import 'package:flutter/material.dart';
class KeyListenerWithCheckBox extends StatefulWidget {
const KeyListenerWithCheckBox({Key? key}) : super(key: key);
@override
_KeyListenerWithCheckBoxState createState() => _KeyListenerWithCheckBoxState();
}
class _KeyListenerWithCheckBoxState extends State<KeyListenerWithCheckBox> {
final focusNodeForCheckBox = FocusNode();
bool isChecked = false;
String text = "";
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Key listener with check box"),
),
body: _generateListenerAndCheckBox(),
),
);
}
Widget _generateListenerAndCheckBox() {
final checkBoxAndListener = generateContainer(
child: KeyboardListener(
focusNode: focusNodeForCheckBox,
onKeyEvent: (event) {
setState(() {
text = "logical: ${event.logicalKey.keyLabel}\n" + "physical: ${event.physicalKey.debugName}";
});
},
child: Checkbox(
value: isChecked,
onChanged: (value) {
setState(() => isChecked = !focusNodeForCheckBox.hasFocus);
if (!focusNodeForCheckBox.hasFocus) {
focusNodeForCheckBox.requestFocus();
} else {
focusNodeForCheckBox.nextFocus();
}
},
),
),
);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
checkBoxAndListener,
generateContainer(
child: Center(child: Text(text)),
),
],
);
}
}
// common.dart
Widget generateContainer({required Widget child, double? height, double? width}) {
return Container(
height: height ?? 100,
width: width ?? 200,
child: child,
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 3),
color: Colors.blue.shade50,
),
);
}
FocusNode
offers both previousFocus()
and nextFocus()
but the behavior of previousFocus()
is a bit strange. It returns true if it successfully found a node and requested focus. However, the child still has the focus even though the return value is true for the first call. So, we use nextFocus()
here.
Let’s check the behavior.
The key press event is detected while the check box is checked. Otherwise, the text in the right container doesn’t change.
Note that there are logicalKey
and physicalKey
. physicalKey
is used for example when we want to use a key next to CAPS key regardless of the keyboard layout. On the other hand, logicalKey
can be used if we want to use “Y” to proceed with a procedure. The key where “Y” is located on QWERTY keyboard is “Z” on the German keyboard layout. A user needs to press a key next to the right shift key for “Y” on the German keyboard.
Use the following constant value to compare the user input.
PhysicalKeyboardKey.delete
LogicalKeyboardKey.delete
The complete code for key detection with CheckBox is in my GitHub repository.
How to detect a key press event on DataTable
Let’s check how DataTable implementation looks like. One data row has 4 properties.
class MyRowDataClass {
bool selected = false;
String text1;
String text2;
String text3;
MyRowDataClass({
required this.text1,
required this.text2,
required this.text3,
});
}
The class is used to create DataRow
.
class _KeyDetectionOnTableState extends State<KeyDetectionOnTable> {
final columnCount = 3;
final rowCount = 10;
List<MyRowDataClass> data = [];
Widget _generateDataTable() {
return DataTable(
showCheckboxColumn: true,
columns: List.generate(
columnCount,
(index) => DataColumn(label: Text("Column-$index")),
),
rows: data.map((row) => _generateDataRow(row)).toList(),
);
}
DataRow _generateDataRow(MyRowDataClass row) {
return DataRow(
selected: row.selected,
cells: [
DataCell(Text(row.text1)),
DataCell(Text(row.text2)),
DataCell(Text(row.text3)),
],
onSelectChanged: (value) {
setState(() {
row.selected = value ?? false;
});
},
);
}
}
The table looks like this.
But a big problem here is that DataRow
is not a widget. Therefore, it’s impossible to wrap it by KeyboardListener
. So, if we want to use KeyboardListener
for DataTable
, we must wrap DataTable
or parent widget.
If you wrap DataTable
, remember that the key press event is not triggered if the focus is away from the DataTable
. A user might press TAB key to move the focus. A user needs to move the focus back to the DataTable
to do something with a key.
If a key event always needs to be triggered, KeyboardListener
needs to be put to the top level to the view. Each key event is defined in onKeyEvent
.
class _KeyDetectionOnTableState extends State<KeyDetectionOnTable> {
final focusNodeForDataTable = FocusNode();
@override
Widget build(BuildContext context) {
return SafeArea(
child: KeyboardListener(
autofocus: true,
focusNode: focusNodeForDataTable,
onKeyEvent: (value) {
debugPrint("Key: ${value.logicalKey.keyLabel}");
if (value.logicalKey == LogicalKeyboardKey.delete) {
setState(() {
text = value.logicalKey.keyLabel;
data.removeWhere((element) => element.selected);
});
} else {
setState(() {
text = value.logicalKey.keyLabel;
});
}
},
child: Scaffold(
// DataTable is in the child
To delete the selected items, filter the List by element.selected
property that indicates that the row is selected. Then, remove them.
Let’s check the behavior in this video.
The key events are detected without checking any CheckBox. Delete key is pressed but it doesn’t delete any rows. But the rows are deleted after selecting them.
The complete code for key detection with DataTable is in my GitHub repository.
Comments