ワンクリックで
add-debounce
// Add debounce to delay method execution until after a period of inactivity for search or validation
// Add debounce to delay method execution until after a period of inactivity for search or validation
Add custom error handling with catchError to a mix() call for logging, error conversion, or suppression
Add internet connectivity checking before executing a Cubit method with optional retry
Add EffectQueue to state for triggering multiple sequential one-time UI effects
Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing
Add error state handling to widgets using context.isFailed() and context.getException()
Add freshness caching to prevent redundant method executions within a time period
| name | add-debounce |
| description | Add debounce to delay method execution until after a period of inactivity for search or validation |
This skill delays method execution until after a period of inactivity, useful for search-as-you-type and form validation.
Adds the debounce parameter to a mix() call so that:
Ask the user which Cubit method needs debouncing, or identify methods that:
Add debounce: debounce to the mix() call:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class SearchCubit extends Cubit<SearchState> {
SearchCubit() : super(const SearchState());
void search(String query) => mix(
key: this,
debounce: debounce, // Add this line (default: 300ms)
() async {
final results = await api.search(query);
emit(state.copyWith(results: results));
},
);
}
The default debounce duration is 300 milliseconds. Customize as needed:
void search(String query) => mix(
key: this,
debounce: debounce(duration: 500.millis), // Wait 500ms of inactivity
() async {
final results = await api.search(query);
emit(state.copyWith(results: results));
},
);
User types: "h" → "he" → "hel" → "hell" → "hello"
↓ ↓ ↓ ↓ ↓
reset reset reset reset timer starts
↓
300ms passes
↓
search("hello") executes
Instead of 5 API calls, only 1 call is made with the final value.
debounce // 300ms (default)
debounce(duration: 100.millis) // 100ms (faster response)
debounce(duration: 500.millis) // 500ms (more delay)
debounce(duration: 1.sec) // 1 second
Different parameters can have separate debounce timers:
void searchInCategory(String category, String query) => mix(
key: this,
debounce: debounce(key: (SearchCubit, category)),
() async {
final results = await api.searchInCategory(category, query);
emit(state.copyWith(results: results));
},
);
With this setup:
class SearchCubit extends Cubit<SearchState> {
SearchCubit() : super(const SearchState());
void search(String query) => mix(
key: this,
debounce: debounce(duration: 300.millis),
() async {
if (query.isEmpty) {
emit(state.copyWith(results: []));
return;
}
final results = await api.search(query);
emit(state.copyWith(results: results));
},
);
}
// In widget
TextField(
onChanged: (value) => context.read<SearchCubit>().search(value),
)
void validateEmail(String email) => mix(
key: ValidateEmail,
debounce: debounce(duration: 500.millis),
() async {
final isValid = await api.checkEmailAvailable(email);
emit(state.copyWith(emailError: isValid ? null : 'Email taken'));
},
);
void autoSave(String content) => mix(
key: AutoSave,
debounce: debounce(duration: 2.sec), // Save after 2 seconds of inactivity
() async {
await api.saveDraft(content);
emit(state.copyWith(lastSaved: DateTime.now()));
},
);
void search(String query) => mix(
key: this,
debounce: debounce(duration: 300.millis),
() async {
final results = await api.search(query);
emit(state.copyWith(results: results));
},
);
// In widget
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: (value) => context.read<SearchCubit>().search(value),
),
if (context.isWaiting(SearchCubit))
const LinearProgressIndicator(),
// ... results list
],
);
}
| Feature | Behavior | Best For |
|---|---|---|
| Debounce | Executes after inactivity | Search, validation, auto-save |
| Throttle | Executes first call, ignores rest | Scroll, resize, refresh |
Debounce example: User types "hello"
Throttle example: User scrolls rapidly
class SearchCubit extends Cubit<SearchState> {
SearchCubit() : super(const SearchState());
void search(String query) => mix(
key: this,
debounce: debounce(duration: 300.millis),
() async {
if (query.trim().isEmpty) {
emit(state.copyWith(results: [], query: ''));
return;
}
final results = await api.search(query);
emit(state.copyWith(results: results, query: query));
},
);
void searchInCategory(String category, String query) => mix(
key: (Search, category), // State tracking per category
debounce: debounce(key: (Search, category)), // Debounce per category
() async {
final results = await api.searchInCategory(category, query);
emit(state.copyWith(
categoryResults: {...state.categoryResults, category: results},
));
},
);
}
// Widget
class SearchScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final state = context.watch<SearchCubit>().state;
return Column(
children: [
TextField(
decoration: const InputDecoration(hintText: 'Search...'),
onChanged: (value) => context.read<SearchCubit>().search(value),
),
if (context.isWaiting(SearchCubit))
const LinearProgressIndicator(),
Expanded(
child: ListView.builder(
itemCount: state.results.length,
itemBuilder: (context, index) => ListTile(
title: Text(state.results[index].title),
),
),
),
],
);
}
}
Ask the user: