بنقرة واحدة
add-effect-queue
Add EffectQueue to state for triggering multiple sequential one-time UI effects
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Add EffectQueue to state for triggering multiple sequential one-time UI effects
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
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 debounce to delay method execution until after a period of inactivity for search or validation
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-effect-queue |
| description | Add EffectQueue to state for triggering multiple sequential one-time UI effects |
This skill adds an EffectQueue field to a state class for sequenced one-time UI effects.
Adds EffectQueue<T> to state for triggering multiple side effects in order:
This pattern eliminates the need for BlocListener. The Cubit declares what should happen
(the effects), while the widget determines how to execute them (the UI logic).
Use EffectQueue when:
Use simple Effect<T> when:
Create a sealed class hierarchy for your effects:
sealed class UiEffect {}
class ShowToast extends UiEffect {
final String message;
ShowToast(this.message);
}
class ShowDialog extends UiEffect {
final String title;
final String content;
ShowDialog(this.title, this.content);
}
class Navigate extends UiEffect {
final String route;
Navigate(this.route);
}
class ClearForm extends UiEffect {}
Add an EffectQueue<UiEffect> field. Always initialize as spent:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class AppState {
final User? user;
final EffectQueue<UiEffect> effectQueue;
AppState({
this.user,
EffectQueue<UiEffect>? effectQueue,
}) : effectQueue = effectQueue ?? EffectQueue.spent();
AppState copyWith({
User? user,
EffectQueue<UiEffect>? effectQueue,
}) {
return AppState(
user: user ?? this.user,
effectQueue: effectQueue ?? this.effectQueue,
);
}
}
Create an EffectQueue with a list of effects and a callback for remaining effects:
class AppCubit extends Cubit<AppState> {
AppCubit() : super(AppState());
void onPurchaseComplete() {
emit(state.copyWith(
effectQueue: EffectQueue<UiEffect>(
[
ShowToast('Purchase successful!'),
ShowDialog('Thank You', 'Your order has been placed.'),
Navigate('/orders'),
],
// Callback to emit remaining effects
(remaining) => emit(state.copyWith(effectQueue: remaining)),
),
));
}
}
Use context.effectQueue() in the build method:
class AppScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
context.effectQueue<AppCubit, UiEffect>(
// Select the queue
(cubit) => cubit.state.effectQueue,
// Handle each effect
(context, effect) => switch (effect) {
ShowToast(:final message) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
),
ShowDialog(:final title, :final content) =>
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
),
Navigate(:final route) =>
Navigator.of(context).pushNamed(route),
ClearForm() =>
_formKey.currentState?.reset(),
},
);
return Scaffold(
body: MyContent(),
);
}
}
Effects execute one at a time, with a rebuild between each:
context.effectQueue<AppCubit, UiEffect>(
(cubit) => cubit.state.effectQueue,
onePerFrame: true, // Default
(context, effect) => ...,
);
All effects execute in a single frame:
context.effectQueue<AppCubit, UiEffect>(
(cubit) => cubit.state.effectQueue,
onePerFrame: false, // Execute all immediately
(context, effect) => ...,
);
sealed class OnboardingEffect {}
class ShowWelcome extends OnboardingEffect {}
class RequestPermissions extends OnboardingEffect {}
class ShowTutorial extends OnboardingEffect {}
class NavigateToHome extends OnboardingEffect {}
void startOnboarding() {
emit(state.copyWith(
effectQueue: EffectQueue<OnboardingEffect>(
[
ShowWelcome(),
RequestPermissions(),
ShowTutorial(),
NavigateToHome(),
],
(remaining) => emit(state.copyWith(effectQueue: remaining)),
),
));
}
sealed class FormEffect {}
class ShowSaving extends FormEffect {}
class ShowSuccess extends FormEffect {
final String message;
ShowSuccess(this.message);
}
class ClearForm extends FormEffect {}
class NavigateBack extends FormEffect {}
void submitForm(FormData data) => mix(
key: this,
() async {
await api.submit(data);
emit(state.copyWith(
effectQueue: EffectQueue<FormEffect>(
[
ShowSuccess('Form submitted!'),
ClearForm(),
NavigateBack(),
],
(remaining) => emit(state.copyWith(effectQueue: remaining)),
),
));
},
);
sealed class ErrorEffect {}
class ShowError extends ErrorEffect {
final String message;
ShowError(this.message);
}
class OfferRetry extends ErrorEffect {
final VoidCallback onRetry;
OfferRetry(this.onRetry);
}
class LogError extends ErrorEffect {
final Object error;
LogError(this.error);
}
void onError(Object error) {
emit(state.copyWith(
effectQueue: EffectQueue<ErrorEffect>(
[
LogError(error),
ShowError('Something went wrong'),
OfferRetry(() => loadData()),
],
(remaining) => emit(state.copyWith(effectQueue: remaining)),
),
));
}
// Effects
sealed class CheckoutEffect {}
class ShowProcessing extends CheckoutEffect {}
class ShowSuccess extends CheckoutEffect {
final String orderId;
ShowSuccess(this.orderId);
}
class SendConfirmationEmail extends CheckoutEffect {
final String email;
SendConfirmationEmail(this.email);
}
class NavigateToOrder extends CheckoutEffect {
final String orderId;
NavigateToOrder(this.orderId);
}
// State
class CheckoutState {
final Cart cart;
final EffectQueue<CheckoutEffect> effectQueue;
CheckoutState({
required this.cart,
EffectQueue<CheckoutEffect>? effectQueue,
}) : effectQueue = effectQueue ?? EffectQueue.spent();
CheckoutState copyWith({
Cart? cart,
EffectQueue<CheckoutEffect>? effectQueue,
}) => CheckoutState(
cart: cart ?? this.cart,
effectQueue: effectQueue ?? this.effectQueue,
);
}
// Cubit
class CheckoutCubit extends Cubit<CheckoutState> {
CheckoutCubit(Cart cart) : super(CheckoutState(cart: cart));
void placeOrder(String email) => mix(
key: this,
() async {
final order = await api.placeOrder(state.cart);
emit(state.copyWith(
cart: Cart.empty(),
effectQueue: EffectQueue<CheckoutEffect>(
[
ShowSuccess(order.id),
SendConfirmationEmail(email),
NavigateToOrder(order.id),
],
(remaining) => emit(state.copyWith(effectQueue: remaining)),
),
));
},
);
}
// Widget
class CheckoutScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
context.effectQueue<CheckoutCubit, CheckoutEffect>(
(cubit) => cubit.state.effectQueue,
onePerFrame: true,
(context, effect) => switch (effect) {
ShowProcessing() =>
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => const AlertDialog(
content: CircularProgressIndicator(),
),
),
ShowSuccess(:final orderId) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Order $orderId placed!')),
),
SendConfirmationEmail(:final email) =>
emailService.sendConfirmation(email),
NavigateToOrder(:final orderId) =>
Navigator.pushReplacementNamed(
context,
'/order/$orderId',
),
},
);
return CheckoutForm();
}
}
Ask the user: