skip to content

In a Flutter email list, why does adding onDoubleTap to a message tile make its onTap fire noticeably late?

level: middleimportance: should knowfreq 30%

answer

  1. someone holds the arena
  2. sweep deferred while held
  3. kDoubleTapTimeout is 300 ms
  4. release runs the pending sweep
  5. onTapDown then onTapCancel

basics

~20 s

DoubleTapGestureRecognizer holds the pointer's gesture arena after the first tap lifts, deferring the sweep that would award the single tap; only after kDoubleTapTimeout (300 ms) passes without a second tap does it release, and onTap fires.

solid answer

~30 s

The arena usually awards an undecided tap on pointer up by sweeping it. When the detector also has `onDoubleTap`, the `DoubleTapGestureRecognizer` calls `hold` on the first pointer's arena, so that sweep is deferred. If no second tap arrives within `kDoubleTapTimeout`, 300 ms, it releases the arena, the pending sweep runs and the single tap wins; if one does, the double tap accepts and the tap is rejected. So every single tap on that tile waits about 300 ms. For a primary action such as opening an email, keep only `onTap` and put starring on a visible button or a long press.

code

dart · 29 lines
dart
import 'package:flutter/material.dart';

class MessageTile extends StatelessWidget {
  const MessageTile({
    super.key,
    required this.subject,
    required this.onOpen,
    required this.onStar,
  });

  final String subject;
  final VoidCallback onOpen;
  final VoidCallback onStar;

  @override
  Widget build(BuildContext context) {
    // Double tap holds the arena, so onTap waits for kDoubleTapTimeout
    // (300 ms) before the message opens. Prefer a visible star button:
    return ListTile(
      title: Text(subject),
      onTap: onOpen, // fires on pointer up, no hold
      trailing: IconButton(
        icon: const Icon(Icons.star_outline),
        tooltip: 'Star',
        onPressed: onStar,
      ),
    );
  }
}

go deeper

for a junior

Remember that adding onDoubleTap makes single taps wait, because Flutter cannot know yet whether a second tap is coming.

for a middle

Explain hold and release on the arena, the deferred sweep, the 300 ms double-tap timeout, and why onTapDown can end in onTapCancel.

for a senior

Choose interactions that keep primary taps immediate, keep pressed states symmetric, and prove arena timing with diagnostics when users report laggy taps.

for a principal

Set interaction guidelines that reserve double tap for places where its latency is acceptable, and prefer visible, accessible controls for secondary actions.

## Holding the arena The gesture arena normally resolves an undecided contest on **pointer up** with a **sweep**: the first member wins. `GestureArenaManager` also lets a member postpone that moment: - **`hold(pointer)`** marks the arena as held. A sweep that arrives while held is **deferred** (`hasPendingSweep`). - **`release(pointer)`** clears the hold; if a sweep was pending, it runs now. Only one recognizer in the framework's common set relies on this: **`DoubleTapGestureRecognizer`**. After the first tap's pointer lifts, it cannot know yet whether a second tap is coming, so it holds the first pointer's arena and starts a timer of **`kDoubleTapTimeout` (300 ms)**. ## Why `onTap` arrives late Give a `GestureDetector` both `onTap` and `onDoubleTap`, and the first pointer's arena contains a tap recognizer and a double-tap recognizer. 1. Pointer up. The double-tap recognizer holds the arena, so the sweep that would award the tap is deferred. 2. If no second tap arrives within 300 ms, the double-tap recognizer gives up and releases; the deferred sweep runs and the tap wins. `onTap` fires roughly 300 ms after the finger lifted. 3. If a second tap arrives in time, the double-tap recognizer accepts, the tap is rejected, and `onDoubleTap` fires instead. In an email list, that means opening a message feels sluggish the moment someone adds "double tap to star". The delay is inherent in the design: the framework cannot report a single tap until it knows it is not the first half of a double tap. ## Why `onTapDown` can be followed by `onTapCancel` A second arena effect confuses people in scrolling lists: - A tap recognizer reports **`onTapDown`** once a short deadline passes (`kPressTimeout`, 100 ms) or once it wins, whichever comes first — even while the arena is still undecided. - If the finger then moves beyond the touch slop, a drag recognizer (the list's scroll) declares victory, the tap is rejected, and the tap recognizer reports **`onTapCancel`**. So pressed-state visuals should start in `onTapDown` and always be undone in both `onTapUp` and `onTapCancel`. `InkWell` does exactly this for its highlight. ## Options for the email tile | Approach | Tap latency | Trade-off | |---|---|---| | `onTap` only | none beyond the sweep | no double-tap shortcut | | `onTap` + `onDoubleTap` | about 300 ms on every single tap | slower opening for every user | | `onTap` + a visible star button | none | uses screen space, but discoverable and accessible | | `onTap` + `onLongPress` for actions | none for taps | long press needs `kLongPressTimeout` (500 ms), but only for the action | A long press does not hold the arena after pointer up; its recognizer competes while the finger is down and wins when the timer fires, so single taps stay immediate. ## Checklist - Avoid `onDoubleTap` on widgets whose single tap is the primary action. - Keep pressed states symmetric across `onTapUp` and `onTapCancel`. - When a tap "sometimes does nothing", log arena resolution with `debugPrintGestureArenaDiagnostics` before blaming hit testing.

  • Does onLongPress on the same tile also delay onTap?
    No. The long-press recognizer competes while the finger is down and wins only if the press lasts `kLongPressTimeout`, 500 ms. If the finger lifts earlier, it has already left the contest and the sweep awards the tap immediately. It never holds the arena after pointer up.
  • Why must a pressed-state highlight also be cleared in onTapCancel?
    `onTapDown` fires after `kPressTimeout`, 100 ms, or on winning, even while the arena is undecided. If a scroll then wins, the tap is rejected and reports `onTapCancel` instead of `onTapUp`, so a highlight cleared only in `onTapUp` would stick.

saying these in an interview costs you the question

  • The delay comes from the double-tap recognizer rendering an extra frame.
  • onTap fires instantly and onDoubleTap cancels it afterwards.
  • A long press holds the arena exactly like a double tap does.
  • onTapDown only fires for taps that go on to win.