flutter_debounce_throttle

pub package License: MIT Tests Coverage

The Traffic Control System for Flutter Apps

Stop using manual Timers. They cause memory leaks and crashes.

All-in-one package for debounce, throttle, rate limiting, and async concurrency control. Memory-safe, lifecycle-aware, and works with any state management solution.

// One widget. Prevents double-tap payment bugs forever.
ThrottledInkWell(
  duration: 500.ms,
  onTap: () => processPayment(),
  child: Text('Pay \$99'),
)

Why Not Just Use easy_debounce?

Capability This Package easy_debounce Manual Timer
Memory Safe (Auto-dispose) ❌ Leaky
Async & Future Support
Race Condition Control ✅ 4 modes
Ready-to-use Widgets
State Management Mixin
Loading States Built-in

How It Works — Visualized

Throttle vs Debounce (Duration: 300ms)

➤ Throttle (Button Clicks)

Executes immediately, then locks for the duration. Subsequent events are ignored.

Events:    (Click1)    (Click2)    (Click3)              (Click4)
Time:      |─ 0ms ─────── 100ms ──── 200ms ──── 300ms ──── 400ms ──|
           ▼                                     ▲
Execution: [EXECUTE] ····················· [LOCKED/DROP] ······· [EXECUTE]
           └─────── 300ms cooldown ──────┘

Use: Payment buttons, save buttons, scroll handlers


➤ Debounce (Search Input)

Waits for a pause in events before executing.

Events:    (Type 'A')   (Type 'B')   (Type 'C')    [User stops typing]
Time:      |─ 0ms ──── 100ms ──── 200ms ────────────── 500ms ──────|
           ▼            ▼            ▼                  ▲
Execution: [WAIT] ····· [RESET] ····· [RESET] ········ [EXECUTE 'ABC']
                                      └─────── 300ms wait ──────┘

Use: Search autocomplete, form validation, window resize


Concurrency Modes (Async)

New task cancels the old one.

Task 1:  [──────── 500ms API Call ──X Cancelled
Task 2:              ↓ New search query
                     [──────── 500ms API Call ────────]  ✅ Result shown

Use: Search autocomplete, tab switching


Mode: drop (Default)

If busy, new tasks are ignored.

Task 1:  [──────── 500ms API Call ────────]  ✅ Completes
Task 2:            ↓ User taps again
                   [DROPPED ❌]

Use: Payment buttons, preventing double-tap


Mode: enqueue

Tasks queue and run in order.

Task 1:  [──────── 500ms ────────]  ✅
Task 2:            ↓ Queued
                   [Waiting...]      [──────── 500ms ────────]  ✅

Use: Chat messages, ordered operations


5-Second Start

Anti-Spam Button:

ThrottledInkWell(onTap: () => pay(), child: Text('Pay'))

Debounced Search:

final debouncer = Debouncer(duration: 300.ms);
TextField(onChanged: (s) => debouncer(() => search(s)))

That's it. No setup. No dispose. Auto-cleanup on widget unmount.


Widgets

Widget Use Case
ThrottledInkWell Button with ripple + throttle
ThrottledBuilder Custom throttled widget
DebouncedBuilder Custom debounced widget
DebouncedQueryBuilder Search with loading state
AsyncThrottledBuilder Async with lock
ConcurrentAsyncThrottledBuilder 4 concurrency modes
StreamDebounceListener Debounce stream events
StreamThrottleListener Throttle stream events

State Management Mixin

Works with Provider, Bloc, GetX, Riverpod, MobX — any ChangeNotifier:

class SearchController with ChangeNotifier, EventLimiterMixin {
  List<User> users = [];

  void onSearch(String text) {
    debounce('search', () async {
      users = await api.search(text);
      notifyListeners();
    });
  }

  @override
  void dispose() {
    cancelAll();  // Clean up all limiters
    super.dispose();
  }
}

⚠️ Important: When using dynamic IDs (e.g., debounce('post_$postId', ...)), you must manually call remove(id) when items are deleted to prevent memory leaks. The mixin does not automatically dispose dynamic IDs. For static IDs like 'search' or 'submit', cancelAll() in dispose is sufficient.


Concurrency Modes

Handle race conditions with 4 strategies:

Mode Behavior Use Case
drop Ignore new while busy Payment buttons
replace Cancel old, run new Search autocomplete
enqueue Queue in order Chat messages
keepLatest Current + last only Auto-save
ConcurrentAsyncThrottledBuilder(
  mode: ConcurrencyMode.replace,  // Cancel stale requests
  builder: (context, throttle, isLoading, pendingCount) => ...
)

Installation

dependencies:
  flutter_debounce_throttle: ^2.0.0

v1.1.0 Features

// Duration extensions
ThrottledInkWell(duration: 500.ms, ...)

// Leading + trailing edge (like lodash)
Debouncer(leading: true, trailing: true)

// Rate limiter with Token Bucket
RateLimiter(maxTokens: 10, refillRate: 2)

// Queue backpressure control
ConcurrentAsyncThrottler(maxQueueSize: 10)

Quality Assurance

Guarantee How
360+ tests Comprehensive unit & integration tests
95% coverage All edge cases covered
Type-safe No dynamic, full generics
Memory-safe Zero leaks verified

Package Use When
dart_debounce_throttle Pure Dart (Server/CLI)
flutter_debounce_throttle_hooks Flutter + Hooks

GitHub · FAQ · API Reference · Best Practices

Made with craftsmanship by Brewkits