Server failure part 1
Data Layer
Summary
This lesson introduces specialized Failure subclasses to improve error handling within the Data Layer. Returning a generic Failure object prevents the UI from providing actionable, specific feedback to the user. By refactoring the base Failure class into an abstract class and creating specific types like ServerFailure, CacheFailure, and NetworkFailure, the application can now pinpoint the origin of an error. The Repository implementation is then updated to return a ServerFailure when catching exceptions from the Remote Data Source.
Key Ideas
- Generic exceptions should not be passed to the UI; they must be mapped to expressive, domain-specific failures.
- Failure is converted into an abstract class to serve as a base type for all application errors.
- Specific failure classes (ServerFailure, CacheFailure, NetworkFailure) are created extending the base Failure.
- HomeRepoImpl is updated to return left(ServerFailure()) when the remote data source throws an exception.
Code Snippets
abstract class Failure {}
class ServerFailure extends Failure {}
class CacheFailure extends Failure {}
class NetworkFailure extends Failure {} @override
Future<Either<Failure, List<BookEntity>>> fetchFeaturedBooks() async {
try {
var booksList = homeLocalDataSource.fetchFeaturedBooks();
if (booksList.isNotEmpty) {
return right(booksList);
}
booksList = await homeRemoteDataSource.fetchFeaturedBooks();
return right(booksList);
} catch (e) {
return left(ServerFailure());
}
}
@override
Future<Either<Failure, List<BookEntity>>> fetchNewestBooks() async {
try {
var booksList = homeLocalDataSource.fetchNewestBooks();
if (booksList.isNotEmpty) {
return right(booksList);
}
booksList = await homeRemoteDataSource.fetchNewestBooks();
return right(booksList);
} catch (e) {
return left(ServerFailure());
}
}Architecture Insight
Failure is the domain-side vocabulary for things going wrong. ServerFailure, CacheFailure, ConnectionFailure — these are categories the UI knows how to show, not raw exception types. The repo impl is responsible for the translation.
A reasonable starting set: ServerFailure(message) for anything the API said went wrong, ConnectionFailure() for no-network, CacheFailure(message) for local-store issues. Add more only when the UI actually needs to react differently.
My Notes
The core architectural principle here is error translation. The Repository layer acts as a boundary that catches infrastructure/data exceptions (like HTTP errors or Database faults) and translates them into domain-level Failure objects.
- [Editor's note] The current implementation simply returns ServerFailure() blindly on any catch. This is a temporary stepping stone. In a production app, we need to inspect catch (e) (typically a DioException if using Dio) and extract the HTTP status code or error message to pass into the ServerFailure constructor. The instructor explicitly defers this logic to the next lesson.
- [Editor's note] The instructor states try/catch is computationally expensive and skips it for Hive local reads. While Dart's try/catch overhead is generally minimal, skipping it for local storage reads is risky if the local database gets corrupted. Always wrap local I/O in try/catch in enterprise apps.