Error Handling
Standardized error handling, loading states, and empty states patterns
Entity System Error Handling Best Practices
Overview
The Vyuh Entity System provides standardized state widgets for consistent error handling, loading states, and empty states across all entity views. All entity layouts should use these standardized widgets instead of implementing custom error handling.
Standardized State Widgets
1. EntityLoadingState<T>
Use for showing loading indicators with entity context.
if (_isLoading.value) {
return const EntityLoadingState<Equipment>(
message: 'Loading equipment data...', // Optional custom message
showEntityContext: true, // Shows entity icon and name
);
}2. EntityErrorState<T>
Use for displaying errors with retry functionality.
if (_error.value != null) {
return EntityErrorState<Equipment>(
error: _error.value,
title: 'Error loading equipment', // Optional custom title
onRetry: _fetchData, // Retry callback
showRetryButton: true, // Show retry button (default: true)
);
}3. EntityEmptyState<T>
Use for empty data states.
if (_items.isEmpty) {
return const EntityEmptyState<Equipment>(
message: 'No equipment found', // Optional custom message
description: 'Create your first equipment to get started', // Optional description
showCreateButton: true, // Show create button (default: true)
onCreatePressed: null, // Optional custom create handler
);
}Implementation Pattern
Here's the recommended pattern for entity layouts:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_api_client/shared_api_client.dart';
import 'package:vyuh_core/vyuh_core.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
class MyEntityLayout extends LayoutConfiguration<MyEntity> {
@override
Widget build(BuildContext context, MyEntity entity) {
return _MyEntityView(entity: entity);
}
}
class _MyEntityView extends StatefulWidget {
final MyEntity entity;
const _MyEntityView({required this.entity});
@override
State<_MyEntityView> createState() => _MyEntityViewState();
}
class _MyEntityViewState extends State<_MyEntityView> {
late final ObservableList<dynamic> _items = ObservableList<dynamic>();
final Observable<bool> _isLoading = Observable(true);
final Observable<String?> _error = Observable(null);
@override
void initState() {
super.initState();
_fetchData();
}
Future<void> _fetchData() async {
try {
runInAction(() {
_isLoading.value = true;
_error.value = null;
});
// Fetch data...
final api = vyuh.di.get<SharedApiClient>();
final response = await api.get('/api/endpoint');
if (response.statusCode != 200) {
throw Exception('Failed to load data: ${response.statusCode}');
}
final responseData = jsonDecode(response.body);
if (responseData['success'] != true) {
throw Exception('API error: ${responseData['error']}');
}
runInAction(() {
_items.clear();
_items.addAll(responseData['data'] as List);
_isLoading.value = false;
});
} catch (e) {
runInAction(() {
_error.value = e.toString();
_isLoading.value = false;
});
}
}
@override
Widget build(BuildContext context) {
return Observer(
builder: (_) {
// Use standardized loading state
if (_isLoading.value) {
return const EntityLoadingState<MyEntity>(
message: 'Loading data...',
);
}
// Use standardized error state
if (_error.value != null) {
return EntityErrorState<MyEntity>(
error: _error.value,
title: 'Error loading data',
onRetry: _fetchData,
);
}
// Use standardized empty state
if (_items.isEmpty) {
return const EntityEmptyState<MyEntity>(
message: 'No data found',
description: 'Add some data to see it here',
showCreateButton: false,
);
}
// Normal content
return ListView.builder(
// ... your list implementation
);
},
);
}
}Benefits
- Consistency: All entity views have the same look and feel for loading, error, and empty states
- Maintainability: Changes to error handling can be made in one place
- Entity Awareness: The widgets automatically use entity metadata (icon, name, theme color)
- Better UX: Users get consistent, professional error messages with clear actions
- Accessibility: Standardized widgets include proper accessibility support
Migration Guide
To migrate existing layouts:
- Add import:
import 'package:vyuh_entity_system/vyuh_entity_system.dart'; - Replace
Center(child: CircularProgressIndicator())withEntityLoadingState<T> - Replace custom error UI with
EntityErrorState<T> - Replace custom empty state UI with
EntityEmptyState<T> - Ensure the generic type parameter matches your entity type
Common Mistakes to Avoid
- Don't use custom error UI: Always use the standardized widgets
- Don't forget the generic type: Always specify the entity type (e.g.,
EntityErrorState<Equipment>) - Don't hide retry buttons: Unless there's a specific reason, always provide retry functionality
- Don't use generic messages: Provide context-specific messages when possible
Examples from the Codebase
Good examples of standardized error handling can be found in:
equipment_areas_layout.dartequipment_user_groups_layout.dartarea_user_groups_layout.dartuser_areas_layout.dartuser_equipment_layout.dartlocation_users_layout.dart