flutter_debounce_throttle
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)
Mode: replace (Perfect for Search)
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 callremove(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 |
Related Packages
| 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