error-handling

SKILL.md

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:

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:

final class OrdersRepository implements IOrdersRepository {
  OrdersRepository({required this.remote});
  final IOrdersRemoteDataSource remote;

  
  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:

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:

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:

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>()));
  });
}
Weekly Installs
1
GitHub Stars
3
First Seen
Feb 27, 2026
Installed on
amp1
cline1
openclaw1
opencode1
cursor1
continue1