Add error state handling to widgets using context.isFailed() and context.getException()
This skill adds error state handling to widgets using context.isFailed() and context.getException().
Shows error UI in widgets by:
context.isFailed(key) to check if an operation failedcontext.getException(key) to get the error messageThe Cubit method must use mix() with a key:
class UserCubit extends Cubit<User?> {
void loadUser() => mix(
key: this,
() async {
final user = await api.getUser();
if (user == null) throw UserException('User not found');
emit(user);
},
);
}
Check context.isFailed(key) in the widget's build method:
class UserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Check if failed
if (context.isFailed(UserCubit)) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Error: ${context.getException(UserCubit)}'),
ElevatedButton(
onPressed: () => context.read<UserCubit>().loadUser(),
child: const Text('Retry'),
),
],
),
);
}
// Check if loading
if (context.isWaiting(UserCubit)) {
return const Center(child: CircularProgressIndicator());
}
// Show data
final user = context.watch<UserCubit>().state;
return Text('Hello, ${user?.name}');
}
}
| Extension | Returns | Description |
|-----------|---------|-------------|
| context.isFailed(key) | bool | True if operation threw an exception |
| context.getException(key) | UserException? | The exception that was thrown |
| context.clearException(key) | void | Manually clears the error state |
Widget build(BuildContext context) {
if (context.isFailed(UserCubit)) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64, color: Colors.red),
const SizedBox(height: 16),
Text(
'${context.getException(UserCubit)}',
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.read<UserCubit>().loadUser(),
child: const Text('Try Again'),
),
],
),
),
);
}
// ...
}
Widget build(BuildContext context) {
return Column(
children: [
if (context.isFailed(UserCubit))
MaterialBanner(
content: Text('${context.getException(UserCubit)}'),
actions: [
TextButton(
onPressed: () => context.read<UserCubit>().loadUser(),
child: const Text('Retry'),
),
TextButton(
onPressed: () => context.clearException(UserCubit),
child: const Text('Dismiss'),
),
],
),
// Rest of content
UserContent(),
],
);
}
Show error but keep displaying old data:
Widget build(BuildContext context) {
final state = context.watch<ProductCubit>().state;
return Column(
children: [
// Show error banner if failed, but keep showing products
if (context.isFailed(ProductCubit))
Container(
color: Colors.red.shade100,
padding: const EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: Text('Failed to refresh: ${context.getException(ProductCubit)}'),
),
TextButton(
onPressed: () => context.read<ProductCubit>().loadProducts(),
child: const Text('Retry'),
),
],
),
),
// Show existing products even if refresh failed
Expanded(
child: ProductsList(products: state.products),
),
],
);
}
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final key = (DeleteItem, item.id);
// Check error for this specific item
if (context.isFailed(key)) {
return ListTile(
title: Text(item.name),
subtitle: Text(
'Delete failed: ${context.getException(key)}',
style: const TextStyle(color: Colors.red),
),
trailing: IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => cubit.deleteItem(item.id),
),
);
}
return ListTile(
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => cubit.deleteItem(item.id),
),
);
},
);
}
Widget build(BuildContext context) {
return Form(
child: Column(
children: [
// Form fields
TextFormField(...),
TextFormField(...),
// Error message
if (context.isFailed(SubmitForm))
Padding(
padding: const EdgeInsets.all(8),
child: Text(
'${context.getException(SubmitForm)}',
style: const TextStyle(color: Colors.red),
),
),
// Submit button
ElevatedButton(
onPressed: context.isWaiting(SubmitForm)
? null
: () => context.read<FormCubit>().submit(formData),
child: context.isWaiting(SubmitForm)
? const CircularProgressIndicator()
: const Text('Submit'),
),
],
),
);
}
Errors are automatically cleared when:
context.clearException(key)// Error cleared when loadUser() is called again
context.read<UserCubit>().loadUser();
// Manually clear error
context.clearException(UserCubit);
class DataScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Data')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
// Error state - full screen
if (context.isFailed(DataCubit)) {
return _buildErrorView(context);
}
// Loading state
if (context.isWaiting(DataCubit)) {
return const Center(child: CircularProgressIndicator());
}
// Success state
final data = context.watch<DataCubit>().state;
return _buildDataView(context, data);
}
Widget _buildErrorView(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.cloud_off,
size: 64,
color: Colors.grey,
),
const SizedBox(height: 16),
Text(
'${context.getException(DataCubit)}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () => context.read<DataCubit>().loadData(),
icon: const Icon(Icons.refresh),
label: const Text('Try Again'),
),
],
),
),
);
}
Widget _buildDataView(BuildContext context, DataState data) {
return RefreshIndicator(
onRefresh: () async {
context.read<DataCubit>().loadData();
},
child: ListView.builder(
itemCount: data.items.length,
itemBuilder: (context, index) => ListTile(
title: Text(data.items[index].name),
),
),
);
}
}
Ask the user:
npx skills add marcglasberg/add-error-display下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer