Error Handling
1) Catch specific errors, not catch (e)
- Prefer
on SomeException catch (e, st)over a genericcatch. - Reason: a generic catch often hides unexpected bugs (null errors, logic issues) and makes debugging harder.
2) Always capture stack trace
- Use
catch (e, st)and log both. - Common mistake: logging only
e.toString()→ you lose the real location of the crash.
3) Don’t swallow errors
- Common mistake: returning
null/ empty list on failure without telling caller. - Instead: either rethrow or convert to a typed exception and let upper layer decide.
4) Create your own exception types for known failures
- Make an
AppExceptionbase class + specific ones (e.g.,NetworkException,UnauthorizedException,ParsingException). - Benefit: UI/business layer can handle meaningfully (show correct message, retry, logout, etc.).
Example — API call (Bad vs Good)
Bad: generic catch + swallow + no stack trace
dart
Future<User?> fetchUser() async { try { final res = await dio.get('/user'); return User.fromJson(res.data); } catch (e) { print('error: $e'); // no stack trace, poor logging return null; // swallows error, caller can’t react properly }}Good: map known errors to custom exceptions + keep stack trace
dart
abstract class AppException implements Exception { final String message; final Object? cause; final StackTrace? stackTrace; const AppException(this.message, {this.cause, this.stackTrace});}
class NetworkException extends AppException { const NetworkException(super.message, {super.cause, super.stackTrace});}
class UnauthorizedException extends AppException { const UnauthorizedException(super.message, {super.cause, super.stackTrace});}
class ServerException extends AppException { const ServerException(super.message, {super.cause, super.stackTrace});}
Future<User> fetchUser() async { try { final res = await dio.get('/user'); return User.fromJson(res.data); } on DioException catch (e, st) { // Map known Dio failures to your app exceptions final status = e.response?.statusCode;
if (status == 401) { throw UnauthorizedException('Session expired', cause: e, stackTrace: st); } if (e.type == DioExceptionType.connectionTimeout || e.type == DioExceptionType.receiveTimeout) { throw NetworkException('Connection timeout', cause: e, stackTrace: st); }
throw ServerException('Request failed', cause: e, stackTrace: st); }}UI layer handling becomes clean and intentional:
dart
try { final user = await repo.fetchUser();} on UnauthorizedException { // redirect to login} on NetworkException { // show retry} on AppException catch (e) { // show e.message}