Flutter DataTable double tap event on DataRow

eye-catchDart and Flutter
Sponsored links

DataRow is used in DataTable to create a row in the table in Flutter. DataRow has onSelectChaned event to do something when the row is selected but doesn’t have onDoubleTap event.

What should we do if we want double tap or double click event? Do we need to install a package? No, we don’t.

We can detect double tap/click without any other package.

You can check the complete code in my GitHub repo.

Sponsored links

Define the row data class

Firstly, let’s create a class that will be used in the DataTable. The data in this class will be shown on the row.

class DoubleTapTableRowData {
  final String filepath;
  final String dataType;
  final String remark;

  DoubleTapTableRowData({
    required this.filepath,
    required this.dataType,
    required this.remark,
  });

  @override
  bool operator ==(Object other) {
    return other is DoubleTapTableRowData && other.hashCode == hashCode;
  }

  @override
  int get hashCode => Object.hash(filepath, dataType, remark);
}

We need to detect double tap/click when the action is done on the same row. The two methods are overridden here to check whether the item is the same or not. It’s ok to use another way to check it if you find it.

Check the following post if you want to know more about the two methods to compare two objects.

Sponsored links

Define DoubleTapChecker to detect double tap/click on a row

Let’s fulfill the DataTable.

import 'package:flutter/material.dart';

class RowDoubleTap extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _RowDoubleTap();
}

class _RowDoubleTap extends State<RowDoubleTap> {
  List<DoubleTapTableRowData> data = [];
  final doubleTapChecker = _DoubleTapChecker<DoubleTapTableRowData>();
  String doubleTapText = "double tap result here";

  void _initializeData() {
    data = [
      DoubleTapTableRowData(
        filepath: "/home/user/abc.txt",
        dataType: "text",
        remark: "abc text",
      ),
      DoubleTapTableRowData(
        filepath: "/home/user/sound.wav",
        dataType: "wav",
        remark: "special sound",
      ),
      DoubleTapTableRowData(
        filepath: "/home/user/secret",
        dataType: "text",
        remark: "secret into",
      ),
    ];
  }

  @override
  void initState() {
    _initializeData();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Row Double Tap"),
      ),
      body: Center(
        child: Column(
          children: [
            _doubleTapRow(),
            Text(doubleTapText),
          ],
        ),
      ),
    );
  }

  Widget _doubleTapRow() {
    return DataTable(
      showCheckboxColumn: false,
      columns: [
        DataColumn(label: Text('Filepath')),
        DataColumn(label: Text('Data type')),
        DataColumn(label: Text('Remark')),
      ],
      rows: data
          .map(
            (e) => DataRow(
              onSelectChanged: ((selected) {
                setState(() {
                  if (doubleTapChecker.isDoubleTap(e)) {
                    doubleTapText = "Double tapped ${e.filepath}";
                    return;
                  }
                  doubleTapText = "Single tap ${e.filepath}";
                });
              }),
              cells: [
                DataCell(Text(e.filepath)),
                DataCell(Text(e.dataType)),
                DataCell(
                  Text(e.remark),
                  onTap: () => setState(
                      () => doubleTapText = "onTap event for ${e.filepath}"),
                ),
              ],
            ),
          )
          .toList(),
    );
  }
}

The view image is the following.

To apply the same definition to all rows, I used .map() method. onSelectChanged is the main place for this post. As you can see, double tap/click detection is done in the method.

rows: data
  .map(
    (e) => DataRow(
      onSelectChanged: ((selected) {
        setState(() {
          if (doubleTapChecker.isDoubleTap(e)) {
            doubleTapText = "Double tapped ${e.filepath}";
            return;
          }
          doubleTapText = "Single tap ${e.filepath}";
        });
      }),
      cells: [
        DataCell(Text(e.filepath)),
        DataCell(Text(e.dataType)),
        DataCell(
          Text(e.remark),
          onTap: () => setState(
              () => doubleTapText = "onTap event for ${e.filepath}"),
        ),
      ],
    ),
  )
  .toList(),

Then, let’s check the class.

class _DoubleTapChecker<T> {
  T? _lastSelectedItem;
  DateTime _lastTimestamp = DateTime.now();

  bool isDoubleTap(T item) {
    if (_lastSelectedItem == null || _lastSelectedItem != item) {
      _lastSelectedItem = item;
      _lastTimestamp = DateTime.now();
      return false;
    }

    final currentTimestamp = DateTime.now();
    final duration = currentTimestamp.difference(_lastTimestamp).inMilliseconds;
    _lastTimestamp = DateTime.now();
    print("last: $_lastTimestamp, current: $currentTimestamp, duration: $duration");
    return duration < 400;
  }
}

I defined this class in the same file but it’s better to define it in another file to make it reusable. It uses generics. It shouldn’t be a problem if you use it for a different data type.

The following is the initialization part to set item to _lastSelectedItem. It must be set for the first time and if the item is different from the previous one. Note that it might not work if hashCode and == operator are not implemented correctly in the class.

bool isDoubleTap(T item) {
  if (_lastSelectedItem == null || _lastSelectedItem != item) {
    _lastSelectedItem = item;
    _lastTimestamp = DateTime.now();
    return false;
  }

If the same row is tapped/clicked twice, we need to check the duration between the two actions. It can be calculated by difference method. The action is detected as double tap/click if the duration is smaller than the specified time.

It’s up to you to decide how long you want the duration to be but I think 400 is good enough.

final currentTimestamp = DateTime.now();
final duration = currentTimestamp.difference(_lastTimestamp).inMilliseconds;
_lastTimestamp = DateTime.now();
print("last: $_lastTimestamp, current: $currentTimestamp, duration: $duration");
return duration < 400;

Sample Video

Note that double click doesn’t work if onTap event is defined on the cell where you click. onTap is defined for secret info cell. You can see the behavior around 6 seconds in the video.

Related articles

Comments

Copied title and URL