Flutter Throttle Debounce
A comprehensive debouncing and throttling utility for search, API calls, and user interactions in Flutter applications. Optimize performance by controlling the frequency of function executions with easy-to-use, memory-efficient utilities.
Features
β¨ Comprehensive Utilities
- π Debouncer: Delays function execution until after a specified time has passed since the last invocation
- β‘ Throttler: Ensures function is called at most once per specified time interval
- π SearchDebouncer: Specialized debouncer for search inputs with configurable minimum query length
- π ApiThrottler: Advanced rate limiting for API calls with request queuing and deduplication
π― Key Benefits
- π± Cross-Platform: Works on all Flutter platforms (Android, iOS, Web, Desktop)
- π§ Memory Efficient: Automatic cleanup and cancellation of pending operations
- π Type Safe: Full Dart type safety with generic support
- π¦ Zero Dependencies: No external dependencies beyond Flutter SDK
- β‘ High Performance: Optimized for high-frequency operations
- π οΈ Easy Integration: Simple API with comprehensive documentation
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
flutter_throttle_debounce: ^0.0.1
Then run:
flutter pub get
Quick Start
Basic Debouncing
Perfect for search fields, form validation, or any user input where you want to wait for the user to finish typing:
import 'package:flutter_throttle_debounce/flutter_throttle_debounce.dart';
final debouncer = Debouncer(delay: Duration(milliseconds: 500));
// In your widget's onChanged callback:
debouncer.call(() {
performSearch(query);
});
Basic Throttling
Ideal for scroll handlers, button clicks, or any high-frequency events:
final throttler = Throttler(interval: Duration(seconds: 1));
// In your scroll handler:
throttler.call(() {
updateScrollPosition();
});
Search-Specific Debouncing
Specialized for search functionality with minimum query length validation:
final searchDebouncer = SearchDebouncer(
delay: Duration(milliseconds: 300),
minLength: 2,
);
searchDebouncer.search(query, (trimmedQuery) {
performSearch(trimmedQuery);
});
API Rate Limiting
Advanced throttling for API calls with queuing and deduplication:
final apiThrottler = ApiThrottler(
requestsPerInterval: 10,
interval: Duration(minutes: 1),
);
await apiThrottler.call('getUserData', () async {
return await userService.fetchUserData();
});
Comprehensive Usage Examples
1. Search Field with Debouncing
class SearchWidget extends StatefulWidget {
@override
_SearchWidgetState createState() => _SearchWidgetState();
}
class _SearchWidgetState extends State<SearchWidget> {
final _searchDebouncer = SearchDebouncer(
delay: Duration(milliseconds: 300),
minLength: 2,
);
final _searchController = TextEditingController();
List<String> _searchResults = [];
@override
void initState() {
super.initState();
_searchDebouncer.onClearResults = () {
setState(() {
_searchResults.clear();
});
};
}
@override
void dispose() {
_searchDebouncer.dispose();
_searchController.dispose();
super.dispose();
}
void _onSearchChanged(String query) {
_searchDebouncer.search(query, (searchQuery) async {
final results = await performSearch(searchQuery);
setState(() {
_searchResults = results;
});
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _searchController,
onChanged: _onSearchChanged,
decoration: InputDecoration(
labelText: 'Search',
prefixIcon: Icon(Icons.search),
),
),
Expanded(
child: ListView.builder(
itemCount: _searchResults.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_searchResults[index]),
);
},
),
),
],
);
}
}
2. Scroll Event Throttling
class ScrollableList extends StatefulWidget {
@override
_ScrollableListState createState() => _ScrollableListState();
}
class _ScrollableListState extends State<ScrollableList> {
final _scrollController = ScrollController();
final _scrollThrottler = Throttler(interval: Duration(milliseconds: 100));
bool _showScrollToTop = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollThrottler.dispose();
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
_scrollThrottler.callWithParameter(_scrollController.offset, (offset) {
setState(() {
_showScrollToTop = offset > 200;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
controller: _scrollController,
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
floatingActionButton: _showScrollToTop
? FloatingActionButton(
onPressed: () {
_scrollController.animateTo(
0,
duration: Duration(milliseconds: 500),
curve: Curves.easeOut,
);
},
child: Icon(Icons.arrow_upward),
)
: null,
);
}
}
3. API Call Management
class UserService {
final _apiThrottler = ApiThrottler(
requestsPerInterval: 60,
interval: Duration(minutes: 1),
enableQueuing: true,
enableDeduplication: true,
);
Future<User> fetchUser(String userId) async {
return await _apiThrottler.call('fetchUser-$userId', () async {
final response = await http.get(
Uri.parse('https://api.example.com/users/$userId'),
);
return User.fromJson(json.decode(response.body));
});
}
Future<List<User>> searchUsers(String query) async {
return await _apiThrottler.callWithParameter(
'searchUsers',
query,
(searchQuery) async {
final response = await http.get(
Uri.parse('https://api.example.com/users/search?q=$searchQuery'),
);
return (json.decode(response.body) as List)
.map((json) => User.fromJson(json))
.toList();
},
);
}
void dispose() {
_apiThrottler.dispose();
}
}
4. Form Validation with Debouncing
class ValidatedForm extends StatefulWidget {
@override
_ValidatedFormState createState() => _ValidatedFormState();
}
class _ValidatedFormState extends State<ValidatedForm> {
final _emailDebouncer = Debouncer(delay: Duration(milliseconds: 500));
final _usernameDebouncer = Debouncer(delay: Duration(milliseconds: 500));
String? _emailError;
String? _usernameError;
@override
void dispose() {
_emailDebouncer.dispose();
_usernameDebouncer.dispose();
super.dispose();
}
void _validateEmail(String email) {
_emailDebouncer.call(() async {
final isValid = await validateEmailWithServer(email);
setState(() {
_emailError = isValid ? null : 'Email is already taken';
});
});
}
void _validateUsername(String username) {
_usernameDebouncer.call(() async {
final isValid = await validateUsernameWithServer(username);
setState(() {
_usernameError = isValid ? null : 'Username is already taken';
});
});
}
@override
Widget build(BuildContext context) {
return Form(
child: Column(
children: [
TextFormField(
onChanged: _validateEmail,
decoration: InputDecoration(
labelText: 'Email',
errorText: _emailError,
),
),
TextFormField(
onChanged: _validateUsername,
decoration: InputDecoration(
labelText: 'Username',
errorText: _usernameError,
),
),
],
),
);
}
}
API Reference
Debouncer
Delays function execution until after a specified time has passed since the last invocation.
Constructor
Debouncer({
Duration delay = const Duration(milliseconds: 300),
})
Methods
call(VoidCallback callback)
- Execute callback after delaycallWithParameter<T>(T parameter, ParameterCallback<T> callback)
- Execute callback with parametercallAsync(AsyncVoidCallback callback)
- Execute async callbackcallAsyncWithParameter<T>(T parameter, AsyncParameterCallback<T> callback)
- Execute async callback with parametercancel()
- Cancel pending executiondispose()
- Clean up resources
Properties
bool isActive
- Whether there's a pending execution
Throttler
Ensures function is called at most once per specified time interval.
Constructor
Throttler({
Duration interval = const Duration(seconds: 1),
})
Methods
call(VoidCallback callback)
- Execute callback respecting throttle intervalcallWithParameter<T>(T parameter, ParameterCallback<T> callback)
- Execute callback with parametercallAsync(AsyncVoidCallback callback)
- Execute async callbackcallAsyncWithParameter<T>(T parameter, AsyncParameterCallback<T> callback)
- Execute async callback with parametercancel()
- Cancel pending executionreset()
- Reset throttle statedispose()
- Clean up resources
Properties
bool isActive
- Whether there's a pending executionDuration timeUntilNextExecution
- Time until next execution is allowed
SearchDebouncer
Specialized debouncer for search functionality with minimum query length validation.
Constructor
SearchDebouncer({
Duration delay = const Duration(milliseconds: 300),
int minLength = 1,
bool trimQuery = true,
})
Methods
search(String query, ParameterCallback<String> onSearch)
- Perform debounced searchsearchAsync(String query, AsyncParameterCallback<String> onSearch)
- Perform async debounced searchwouldTriggerSearch(String query)
- Check if query would trigger searchcancel()
- Cancel pending searchclear()
- Clear state and canceldispose()
- Clean up resources
Properties
String? lastQuery
- Last processed querybool isActive
- Whether there's a pending searchVoidCallback? onClearResults
- Callback for clearing resultsAsyncVoidCallback? onClearResultsAsync
- Async callback for clearing results
ApiThrottler
Advanced rate limiting for API calls with request queuing and deduplication.
Constructor
ApiThrottler({
int requestsPerInterval = 60,
Duration interval = const Duration(minutes: 1),
bool enableQueuing = true,
bool enableDeduplication = true,
})
Methods
call(String requestKey, AsyncVoidCallback callback)
- Execute throttled API callcallWithParameter<T>(String requestKey, T parameter, AsyncParameterCallback<T> callback)
- Execute with parametercancelQueuedRequests()
- Cancel all queued requestsreset()
- Reset throttler statedispose()
- Clean up resources
Properties
int requestsInCurrentInterval
- Current request countint remainingRequests
- Remaining requests in intervalint queuedRequestsCount
- Number of queued requestsbool isRateLimited
- Whether rate limit is exceededDuration timeUntilNextSlot
- Time until next request slot
Performance Considerations
Memory Usage
- All utilities automatically clean up timers and references
- Always call
dispose()
when no longer needed - Use the utilities as instance variables, not local variables
Best Practices
- Choose appropriate delay/interval values for your use case
- For search: 300-500ms delay is usually optimal
- For scroll events: 100-200ms interval works well
- For API calls: Match your API rate limits
Flutter Integration
- Dispose utilities in
StatefulWidget.dispose()
- Use with controllers and services for better organization
- Consider using with state management solutions
Migration Guide
From other debounce/throttle packages
This package provides a more comprehensive and type-safe API. Here's how to migrate:
// Old package
Timer? _debounceTimer;
void _onSearchChanged(String query) {
_debounceTimer?.cancel();
_debounceTimer = Timer(Duration(milliseconds: 500), () {
performSearch(query);
});
}
// This package
final _searchDebouncer = SearchDebouncer(delay: Duration(milliseconds: 500));
void _onSearchChanged(String query) {
_searchDebouncer.search(query, (searchQuery) {
performSearch(searchQuery);
});
}
Contributing
Contributions are welcome! Please read our contributing guidelines and code of conduct.
Development Setup
- Clone the repository
- Run
flutter pub get
- Run tests:
flutter test
- Run example:
cd example && flutter run
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
See CHANGELOG.md for details about changes in each version.
Support
- π Documentation
- π Issue Tracker
- π¬ Discussions
Made with β€οΈ for the Flutter community
Libraries
- flutter_throttle_debounce
- A comprehensive debouncing and throttling utility for search, API calls, and user interactions.