بنقرة واحدة
add-effect
Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing
التثبيت باستخدام 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 EffectQueue to state for triggering multiple sequential one-time UI effects
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 |
| description | Add Effect fields to state class for one-time UI notifications like snackbars, navigation, or form clearing |
This skill adds an Effect field to a state class for one-time UI notifications.
Adds Effect<T> fields to state for triggering one-time side effects like:
Effects are automatically consumed after being read, ensuring they trigger only once.
Ask the user what one-time action they need:
Add an Effect<T> field to the state class. Always initialize as spent:
import 'package:bloc_superpowers/bloc_superpowers.dart';
class UserState {
final User? user;
final Effect<String> messageEffect; // For showing messages
final Effect<bool> clearFormEffect; // For clearing forms
UserState({
this.user,
Effect<String>? messageEffect,
Effect<bool>? clearFormEffect,
}) : messageEffect = messageEffect ?? Effect.spent(),
clearFormEffect = clearFormEffect ?? Effect.spent();
UserState copyWith({
User? user,
Effect<String>? messageEffect,
Effect<bool>? clearFormEffect,
}) {
return UserState(
user: user ?? this.user,
messageEffect: messageEffect ?? this.messageEffect,
clearFormEffect: clearFormEffect ?? this.clearFormEffect,
);
}
}
Create new effects using Effect(value):
class UserCubit extends Cubit<UserState> {
UserCubit() : super(UserState());
void saveUser(User user) => mix(
key: this,
() async {
await api.saveUser(user);
emit(state.copyWith(
user: user,
messageEffect: Effect('User saved successfully!'),
));
},
);
void clearForm() {
emit(state.copyWith(
clearFormEffect: Effect(true),
));
}
}
Use context.effect() in the build method:
class UserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Consume message effect
final message = context.effect((UserCubit c) => c.state.messageEffect);
if (message != null) {
// Show snackbar after build completes
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
});
}
// Consume clear form effect
final shouldClear = context.effect((UserCubit c) => c.state.clearFormEffect);
if (shouldClear == true) {
_formKey.currentState?.reset();
}
return Scaffold(
body: UserForm(key: _formKey),
);
}
}
For simple triggers without data:
// State
final Effect clearEffect;
// Initialize
clearEffect = clearEffect ?? Effect.spent();
// Emit
emit(state.copyWith(clearEffect: Effect()));
// Consume - returns true if new, false if spent
final shouldClear = context.effect((MyCubit c) => c.state.clearEffect);
if (shouldClear) {
// Do something
}
For effects that carry data:
// State
final Effect<String> messageEffect;
final Effect<String> navigateEffect;
final Effect<int> scrollToEffect;
// Emit
emit(state.copyWith(messageEffect: Effect('Hello!')));
emit(state.copyWith(navigateEffect: Effect('/profile')));
emit(state.copyWith(scrollToEffect: Effect(42)));
// Consume - returns value if new, null if spent
final message = context.effect((MyCubit c) => c.state.messageEffect);
if (message != null) showMessage(message);
final route = context.effect((MyCubit c) => c.state.navigateEffect);
if (route != null) Navigator.pushNamed(context, route);
final index = context.effect((MyCubit c) => c.state.scrollToEffect);
if (index != null) scrollController.jumpTo(index.toDouble());
// State
final Effect<String> snackbarEffect;
// Cubit
void showSuccess() {
emit(state.copyWith(snackbarEffect: Effect('Operation successful!')));
}
// Widget
final message = context.effect((MyCubit c) => c.state.snackbarEffect);
if (message != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
});
}
// State
final Effect<String> navigateEffect;
// Cubit
void onLoginSuccess() {
emit(state.copyWith(navigateEffect: Effect('/home')));
}
// Widget
final route = context.effect((MyCubit c) => c.state.navigateEffect);
if (route != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacementNamed(context, route);
});
}
// State
final Effect<bool> clearTextEffect;
// Cubit
void onMessageSent() {
emit(state.copyWith(clearTextEffect: Effect(true)));
}
// Widget
final shouldClear = context.effect((ChatCubit c) => c.state.clearTextEffect);
if (shouldClear == true) {
_textController.clear();
}
// State
final Effect<String> setTextEffect;
// Cubit
void loadDraft(String text) {
emit(state.copyWith(setTextEffect: Effect(text)));
}
// Widget
final text = context.effect((FormCubit c) => c.state.setTextEffect);
if (text != null) {
_textController.text = text;
}
Always initialize as spent:
messageEffect = messageEffect ?? Effect.spent();
One consumer per effect: Only one widget should consume each effect
Effects are consumed once: After reading, the effect becomes "spent"
Use addPostFrameCallback for dialogs/navigation:
if (effect != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Show dialog, navigate, etc.
});
}
// Create a new effect
Effect() // Untyped
Effect(value) // Typed
// Create a spent effect
Effect.spent() // Untyped
Effect<T>.spent() // Typed
// Check state without consuming
effect.isSpent // true if already consumed
effect.isNotSpent // true if available
effect.state // Get value without consuming
// Consume (marks as spent)
effect.consume() // Returns value (or true/null)
Effects use custom equality to ensure proper rebuild behavior:
This is why emitting Effect('hello') twice triggers two separate rebuilds—each new Effect
instance is unique until consumed.
// State
class ChatState {
final List<Message> messages;
final Effect<bool> clearInputEffect;
final Effect<String> errorEffect;
final Effect<int> scrollToMessageEffect;
ChatState({
this.messages = const [],
Effect<bool>? clearInputEffect,
Effect<String>? errorEffect,
Effect<int>? scrollToMessageEffect,
}) : clearInputEffect = clearInputEffect ?? Effect.spent(),
errorEffect = errorEffect ?? Effect.spent(),
scrollToMessageEffect = scrollToMessageEffect ?? Effect.spent();
ChatState copyWith({
List<Message>? messages,
Effect<bool>? clearInputEffect,
Effect<String>? errorEffect,
Effect<int>? scrollToMessageEffect,
}) => ChatState(
messages: messages ?? this.messages,
clearInputEffect: clearInputEffect ?? this.clearInputEffect,
errorEffect: errorEffect ?? this.errorEffect,
scrollToMessageEffect: scrollToMessageEffect ?? this.scrollToMessageEffect,
);
}
// Cubit
class ChatCubit extends Cubit<ChatState> {
ChatCubit() : super(ChatState());
void sendMessage(String text) => mix(
key: this,
() async {
final message = await api.sendMessage(text);
emit(state.copyWith(
messages: [...state.messages, message],
clearInputEffect: Effect(true),
scrollToMessageEffect: Effect(state.messages.length),
));
},
);
}
// Widget
class ChatScreen extends StatelessWidget {
final _controller = TextEditingController();
final _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
// Clear input after sending
final shouldClear = context.effect((ChatCubit c) => c.state.clearInputEffect);
if (shouldClear == true) {
_controller.clear();
}
// Scroll to new message
final scrollTo = context.effect((ChatCubit c) => c.state.scrollToMessageEffect);
if (scrollTo != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
// Show error
final error = context.effect((ChatCubit c) => c.state.errorEffect);
if (error != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(error), backgroundColor: Colors.red),
);
});
}
return Scaffold(
body: Column(
children: [
Expanded(child: MessageList(controller: _scrollController)),
MessageInput(controller: _controller),
],
),
);
}
}
Ask the user: