Skip to content

A powerful event handling library. Prevents double-clicks, fixes async race conditions, and manages loading states.

License

Notifications You must be signed in to change notification settings

vietnguyentuan2019/flutter_event_limiter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

23 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Flutter Event Limiter ๐Ÿ›ก๏ธ

pub package Pub Points Tests License: MIT

Production-ready throttle and debounce for Flutter apps.

Stop wrestling with Timer boilerplate, race conditions, and setState crashes. Handle button spam, search debouncing, and async operations with 3 lines of code instead of 15+.


โšก Quick Start

Problem: Manual Throttling (15+ lines, error-prone)

Timer? _timer;
bool _loading = false;

void onSearch(String text) {
  _timer?.cancel();
  _timer = Timer(Duration(milliseconds: 300), () async {
    setState(() => _loading = true);
    try {
      final result = await api.search(text);
      if (!mounted) return; // Easy to forget!
      setState(() => _result = result);
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  });
}

@override
void dispose() {
  _timer?.cancel(); // Easy to forget!
  super.dispose();
}

Solution: flutter_event_limiter (3 lines, safe)

AsyncDebouncedTextController(
  onChanged: (text) async => await api.search(text),
  onSuccess: (result) => setState(() => _result = result),
  onLoadingChanged: (loading) => setState(() => _loading = loading),
)

Result: 80% less code. Auto-dispose. Auto mounted checks. Auto loading state.


โœจ Why This Library?

Built for production use:

  • โœ… 160/160 pub points - Perfect score, actively maintained
  • โœ… 128 comprehensive tests - Edge cases covered, battle-tested
  • โœ… Zero dependencies - No bloat, no conflicts

Saves development time:

  • โœ… 3 lines vs 15+ - Eliminates boilerplate
  • โœ… Auto-safety - No setState crashes, no memory leaks
  • โœ… Works with any widget - Material, Cupertino, custom widgets

Unique features:

  • โœ… Built-in loading state - No manual bool isLoading = false needed
  • โœ… Race condition prevention - Auto-cancels stale API calls
  • โœ… Universal builders - Not locked into specific widgets

See detailed comparison with alternatives โ†’


๐Ÿš€ Common Use Cases

1. Prevent Button Double-Clicks

ThrottledInkWell(
  onTap: () => submitOrder(), // Only executes once per 500ms
  child: Text("Submit Order"),
)

See E-Commerce example โ†’

2. Smart Search Bar

AsyncDebouncedTextController(
  duration: Duration(milliseconds: 300),
  onChanged: (text) async => await api.search(text),
  onSuccess: (products) => setState(() => _products = products),
  onLoadingChanged: (isLoading) => setState(() => _loading = isLoading),
)

See Search example โ†’

3. Form Submission with Loading UI

AsyncThrottledCallbackBuilder(
  onPressed: () async => await uploadFile(),
  builder: (context, callback, isLoading) {
    return ElevatedButton(
      onPressed: isLoading ? null : callback,
      child: isLoading ? CircularProgressIndicator() : Text("Upload"),
    );
  },
)

See Form example โ†’

4. Advanced Concurrency Control (NEW in v1.2.0)

Control how multiple async operations are handled with 4 powerful modes:

// Chat App: Queue messages and send in order
ConcurrentAsyncThrottledBuilder(
  mode: ConcurrencyMode.enqueue,
  onPressed: () async => await api.sendMessage(text),
  builder: (context, callback, isLoading, pendingCount) {
    return ElevatedButton(
      onPressed: callback,
      child: Text(pendingCount > 0 ? 'Sending ($pendingCount)...' : 'Send'),
    );
  },
)

// Search: Cancel old queries, only run latest
ConcurrentAsyncThrottledBuilder(
  mode: ConcurrencyMode.replace,
  onPressed: () async => await api.search(query),
  builder: (context, callback, isLoading, _) {
    return SearchBar(
      onChanged: (q) { query = q; callback?.call(); },
      trailing: isLoading ? CircularProgressIndicator() : null,
    );
  },
)

// Auto-save: Save current + latest only
ConcurrentAsyncThrottledBuilder(
  mode: ConcurrencyMode.keepLatest,
  onPressed: () async => await api.saveDraft(content),
  builder: (context, callback, isLoading, _) {
    return TextField(
      onChanged: (text) { content = text; callback?.call(); },
      decoration: InputDecoration(
        suffixIcon: isLoading ? CircularProgressIndicator() : Icon(Icons.check),
      ),
    );
  },
)

4 Concurrency Modes:

  • ๐Ÿ”ด Drop (default): Ignore new calls while busy - perfect for preventing double-clicks
  • ๐Ÿ“ค Enqueue: Queue all calls and execute sequentially - perfect for chat apps
  • ๐Ÿ”„ Replace: Cancel current and start new - perfect for search queries
  • ๐Ÿ’พ Keep Latest: Execute current + latest only - perfect for auto-save

See interactive demo โ†’


๐ŸŽจ Universal Builder Pattern

The power of flexibility: Works with any Flutter widget.

ThrottledBuilder(
  duration: Duration(seconds: 1),
  builder: (context, throttle) {
    return FloatingActionButton(
      onPressed: throttle(() => saveData()),
      child: Icon(Icons.save),
    );
  },
)

Use with:

  • โœ… Material Design (ElevatedButton, FloatingActionButton, InkWell)
  • โœ… Cupertino (CupertinoButton, CupertinoTextField)
  • โœ… Custom widgets from any package
  • โœ… Third-party UI libraries

Unlike other libraries that lock you into specific widgets, flutter_event_limiter adapts to your UI framework.


๐Ÿ“Š Throttle vs Debounce: Which One?

Throttle (Anti-Spam)

Fires immediately, then blocks for duration.

User clicks: โ–ผ     โ–ผ   โ–ผโ–ผโ–ผ       โ–ผ
Executes:    โœ“     X   X X       โœ“
             |<-500ms->|         |<-500ms->|

Use for: Button clicks, refresh actions, preventing spam

Debounce (Wait for Pause)

Waits for pause in events, then fires.

User types:  a  b  c  d ... (pause) ... e  f  g
Executes:                   โœ“                   โœ“
             |<--300ms wait-->|     |<--300ms wait-->|

Use for: Search input, auto-save, slider changes

AsyncDebouncer (Debounce + Auto-Cancel)

Waits for pause and cancels previous async operations.

User types:  a    b    c  (API starts) ... d
API calls:   X    X    โ–ผ (running...)   X (cancelled)
Result used:                            โœ“ (only 'd')

Use for: Search APIs, autocomplete, async validation

Learn more about timing strategies โ†’


๐Ÿ“š Complete Widget Reference

Throttling (Anti-Spam Buttons)

Widget Use Case
ThrottledInkWell Material buttons with ripple
ThrottledBuilder Universal - Any widget
AsyncThrottledCallbackBuilder Async with auto loading state
Throttler Direct class (advanced)

Debouncing (Search, Auto-save)

Widget Use Case
DebouncedTextController Basic text input
AsyncDebouncedTextController Search API with loading
DebouncedBuilder Universal - Any widget
Debouncer Direct class (advanced)

High-Frequency Events

Widget Use Case
HighFrequencyThrottler Scroll, mouse, resize (60fps)

View full API documentation โ†’


๐Ÿ›  Installation

flutter pub add flutter_event_limiter

Or add to pubspec.yaml:

dependencies:
  flutter_event_limiter: ^1.1.2

Then import:

import 'package:flutter_event_limiter/flutter_event_limiter.dart';

๐Ÿ“– Documentation

Getting Started

Examples

Migration Guides

Advanced


๐ŸŽฏ Features

Core Capabilities:

  • โฑ๏ธ Throttle - Execute immediately, block duplicates
  • โณ Debounce - Wait for pause, then execute
  • ๐Ÿ”„ Async Support - Built-in for async operations
  • ๐Ÿƒ High Frequency - Optimized for scroll/mouse events (60fps)
  • ๐ŸŽญ Combo - ThrottleDebouncer (leading + trailing)

Safety & Reliability:

  • ๐Ÿ›ก๏ธ Auto Dispose - Zero memory leaks
  • โœ… Auto Mounted Checks - No setState crashes
  • ๐Ÿ Race Condition Prevention - Auto-cancel stale calls
  • ๐Ÿ“ฆ Batch Execution - Group multiple operations

Developer Experience:

  • ๐ŸŽจ Universal Builders - Works with any widget
  • ๐Ÿ“Š Built-in Loading State - No manual flags
  • ๐Ÿ› Debug Mode - Log throttle/debounce events
  • ๐Ÿ“ˆ Performance Metrics - Track execution time
  • โš™๏ธ Conditional Execution - Enable/disable dynamically

๐Ÿงช Testing

Works seamlessly with Flutter's test framework:

testWidgets('throttle blocks rapid clicks', (tester) async {
  int clickCount = 0;

  await tester.pumpWidget(
    MaterialApp(
      home: ThrottledInkWell(
        duration: Duration(milliseconds: 500),
        onTap: () => clickCount++,
        child: Text('Tap'),
      ),
    ),
  );

  await tester.tap(find.text('Tap'));
  expect(clickCount, 1);

  await tester.tap(find.text('Tap')); // Blocked
  expect(clickCount, 1);

  await tester.pumpAndSettle(Duration(milliseconds: 500));
  await tester.tap(find.text('Tap')); // Works again
  expect(clickCount, 2);
});

See more testing examples in FAQ โ†’


๐Ÿ”ง Advanced Features (v1.1.0+)

Debug Mode

Throttler(
  debugMode: true,
  name: 'submit-button',
  onMetrics: (duration, executed) {
    print('Throttle took: $duration, executed: $executed');
  },
)

Conditional Throttling

ThrottledBuilder(
  enabled: !isVipUser, // VIP users skip throttle
  builder: (context, throttle) => ElevatedButton(...),
)

Custom Duration per Call

final throttler = Throttler();

throttler.callWithDuration(
  () => criticalAction(),
  duration: Duration(seconds: 2), // Override default
);

Manual Reset

final throttler = Throttler();
throttler.call(() => action());
throttler.reset(); // Clear throttle state
throttler.call(() => action()); // Executes immediately

See all advanced features โ†’


๐Ÿ’ก Integration with State Management

Works with all state management solutions:

GetX:

ThrottledInkWell(
  onTap: () => Get.find<MyController>().submit(),
  child: Text("Submit"),
)

Riverpod:

AsyncDebouncedTextController(
  onChanged: (text) async {
    return await ref.read(searchProvider.notifier).search(text);
  },
  onSuccess: (results) {
    // Update state
  },
)

Bloc:

ThrottledBuilder(
  builder: (context, throttle) {
    return ElevatedButton(
      onPressed: throttle(() => context.read<MyBloc>().add(SubmitEvent())),
      child: Text("Submit"),
    );
  },
)

Provider:

ThrottledInkWell(
  onTap: () => context.read<CounterProvider>().increment(),
  child: Text("Increment"),
)

See more state management examples โ†’


โšก Performance

Near-zero overhead:

Metric Performance
Throttle/Debounce ~0.01ms per call
High-Frequency Throttler ~0.001ms (100x faster)
Memory ~40 bytes per controller

Benchmarked: Handles 1000+ concurrent operations without frame drops.

See performance benchmarks โ†’


๐Ÿค Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new features
  4. Submit a pull request

See CONTRIBUTING.md for guidelines.


๐Ÿ“„ License

MIT License - See LICENSE file for details.


๐Ÿ“ฎ Support


Made with โค๏ธ for the Flutter community