home / skills / yelmuratoff / agent_sync / error-handling
This skill helps you implement typed, layered error handling in Dart/Flutter projects, mapping repository failures to UI states and safe logging.
npx playbooks add skill yelmuratoff/agent_sync --skill error-handlingReview the files below or copy the command above to add this skill to your agents.
---
name: error-handling
description: When designing typed exceptions, mapping low-level failures in repositories, and converting errors to UI states via `handleException` and `ISpect.logger.handle`.
---
# Error Handling (Typed, Layered, Testable)
## When to use
- Introducing a new network/storage flow.
- Handling backend error responses and mapping them to domain-level failures.
- Ensuring BLoC error states are consistent and user-safe.
## Steps
### 1) Define explicit exception types
Keep them small and descriptive:
```dart
final class NetworkException implements Exception {
const NetworkException(this.message, {this.cause});
final String message;
final Object? cause;
}
final class TimeoutAppException implements Exception {
const TimeoutAppException(this.message, {this.cause});
final String message;
final Object? cause;
}
final class ParseException implements Exception {
const ParseException(this.message, {this.cause});
final String message;
final Object? cause;
}
```
### 2) Translate low-level failures in the repository
Repository is the boundary that converts transport/storage details into app-level meaning:
```dart
final class OrdersRepository implements IOrdersRepository {
OrdersRepository({required this.remote});
final IOrdersRemoteDataSource remote;
@override
Future<List<OrderDto>> getOrders() async {
try {
final maps = await remote.fetchOrders();
return maps.map(OrderDto.fromMap).toList();
} on FormatException catch (e) {
throw ParseException('Invalid orders payload', cause: e);
} on TimeoutException catch (e) {
throw TimeoutAppException('Orders request timed out', cause: e);
} catch (e) {
throw NetworkException('Failed to fetch orders', cause: e);
}
}
}
```
### 3) Handle exceptions at async boundaries and log safely
When catching, log via ISpect and include stack trace; never log PII or secrets:
```dart
try {
await repo.getOrders();
} catch (e, st) {
ISpect.logger.handle(
exception: e,
stackTrace: st,
message: 'Orders load failed',
);
rethrow;
}
```
### 4) Convert exceptions into UI state via handleException in BLoC
Use the project’s `handleException` helper to map exceptions to user-facing messages consistently:
```dart
try {
final orders = await repository.getOrders();
emit(OrdersLoadedState(orders: orders));
} catch (e, st) {
handleException(
exception: e,
stackTrace: st,
onError: (message, _, __, ___) => emit(
OrdersErrorState(message: message, error: e, stackTrace: st),
),
);
}
```
### 5) Test error mapping
Unit test that repositories map failures deterministically:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class _RemoteMock extends Mock implements IOrdersRemoteDataSource {}
void main() {
test('maps unknown errors to NetworkException', () async {
final remote = _RemoteMock();
when(() => remote.fetchOrders()).thenThrow(Exception('boom'));
final repo = OrdersRepository(remote: remote);
expect(repo.getOrders(), throwsA(isA<NetworkException>()));
});
}
```
This skill codifies a typed, layered error-handling approach for Dart/Flutter agents. It focuses on defining explicit exception types, translating low-level failures at repository boundaries, safely logging errors, and converting exceptions into consistent UI states via handleException and ISpect.logger.handle. The result is predictable, testable error flows across network, storage, and presentation layers.
Define small, descriptive exception classes that carry a message and optional cause. Repositories catch transport or parsing errors and rethrow domain-level exceptions. Async boundaries log via ISpect.logger.handle with stack traces while avoiding PII, then either rethrow or map errors into UI states using handleException in BLoC. Unit tests assert deterministic mapping from low-level failures to typed exceptions.
Why use typed exceptions instead of plain Exception?
Typed exceptions make intent explicit, enable precise handling and testing, and prevent coupling to transport-specific error types.
Where should I log errors?
Log at async boundaries using ISpect.logger.handle with the exception and stack trace. Avoid logging PII and keep messages contextual but generic for users.