flutter_debounce_throttle

pub package License: MIT Tests Coverage GitHub stars

The Traffic Control System for Flutter Apps

Stop using manual Timer. It causes memory leaks, crashes, and race conditions.

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


30-Second Start

Anti-Spam Button (1 line):

ThrottledInkWell(onTap: () => processPayment(), child: Text('Pay \$99'))

Debounced Search:

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

Async with loading state:

AsyncThrottledBuilder(
  builder: (context, throttle, isLoading) => ElevatedButton(
    onPressed: throttle(() async => await submitForm()),
    child: Text(isLoading ? 'Submitting...' : 'Submit'),
  ),
)

State management (Provider / Riverpod / GetX / Bloc):

class SearchController with ChangeNotifier, EventLimiterMixin {
  void onSearch(String text) {
    debounce('search', () async {
      _results = await api.search(text);
      notifyListeners();
    });
  }

  @override
  void dispose() { cancelAll(); super.dispose(); }
}

No setup. No dispose boilerplate. Auto-cleanup on widget unmount.


Widgets

Widget Use Case
ThrottledInkWell Button with ripple + throttle — prevent double-tap
ThrottledBuilder Custom throttled widget
DebouncedBuilder Custom debounced widget
DebouncedQueryBuilder Search input with loading state
AsyncThrottledBuilder Async button with loading lock
ConcurrentAsyncThrottledBuilder 4 concurrency modes
ThrottledGestureDetector Drop-in GestureDetector replacement
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', ...)), call remove(id) when items are deleted. For static IDs like 'search', cancelAll() in dispose is sufficient.


Concurrency Modes

Handle race conditions in async operations 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 API requests
  builder: (context, throttle, isLoading, pendingCount) => ...
)

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


New task cancels the old one.

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

Extensions & Utilities

// Duration extensions — write durations naturally
ThrottledInkWell(duration: 500.ms, ...)
Debouncer(duration: 300.ms)
RateLimiter(refillInterval: 1.seconds)

// 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)

// Callback extensions
final debouncedFn = myFunction.debounced(300.ms);
final throttledFn = myFunction.throttled(500.ms);

Installation

dependencies:
  flutter_debounce_throttle: ^2.4.4

Quality Assurance

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

Which Package Should I Use?

You are building... Package
Flutter app (most users) flutter_debounce_throttle ← you are here
Flutter app + flutter_hooks flutter_debounce_throttle_hooks
Dart server / CLI / Serverpod dart_debounce_throttle

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

GitHub · FAQ · API Reference · Best Practices · Migration Guide

Made with craftsmanship by Brewkits