Flutter How to delete a row on DataTable by Delete button

eye-catch Dart and Flutter

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.

Sponsored links

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.

I want you to clone my repository and try to check the behavior. I run it as a Desktop app on Linux. Therefore the keyboard is not shown in the video.

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.

Sponsored links

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.

Related Articles

Comments

Copied title and URL