Skip to main content
Async & State ManagementRelease R1

FL02: Debounced Ordered Search

Debounce timing, out-of-order completion guards, query clearance, and disposal lifecycle

The Production Challenge

Naive Flutter search implementations execute async repository calls directly inside `onChanged` without generation tracking. Because network latency is non-deterministic, a slow response for an older query (e.g. "flutter" taking 700ms) can arrive after a fast response for a newer query (e.g. "dart" taking 50ms). Without request identity guards, the older response arrives last and overwrites the newer search results, displaying results for a query the user already discarded. Furthermore, clearing the search field or navigating away while an async call is in-flight often triggers `setState() called after dispose()` exceptions.

Architectural Solution

The solution introduces a clean `DebouncedSearchController` that manages the async search lifecycle with four critical safety mechanisms: 1. **300ms Keystroke Debounce**: Coalesces rapid keystrokes so only the settled query fires a network call. 2. **Monotonic Generation Token (`generationId`)**: Incremented on every search execution and query clear. When an async response completes, `generationToken == _currentGenerationId` is checked. Stale out-of-order responses are discarded immediately. 3. **Synchronous Query Clearing**: Invalidate pending network responses and reset to idle immediately on clear, without waiting for the network to timeout. 4. **Lifecycle-Safe Disposal**: Cancels active debounce timers and detaches listeners to guarantee zero timer leaks or post-dispose state mutations.

Simulated Screen Controls:Adjust constraints and text scaling without mutating source files.
Viewport:
Text Scale:
Interactive Out-of-Order Race Test:

Click "Demo Race Condition" inside the Flutter toolbar above. It fires query "scroll" with 600ms latency, then 80ms later fires query "anim" with 50ms latency. Watch as results for "anim" appear first, and when the delayed "scroll" response arrives at 600ms, it is safely discarded because its generation token does not match the active query!