Implement cubit fetch featured books method
Presentation (Integration)
Summary
This lesson covers the implementation of the fetchFeaturedBooks method within the FeaturedBooksCubit. The instructor demonstrates how the presentation logic holder (Cubit) interacts with the domain layer by calling the FetchFeaturedBooksUseCase rather than accessing the repository directly. By awaiting the use case and using the fold method on the returned Either type, the Cubit gracefully maps the result into appropriate state emissions (Loading, Failure, and Success). This reinforces the separation of concerns, keeping the Cubit focused strictly on state management while delegating data fetching.
Key Ideas
- Cubit methods handling async data should typically start by emitting a Loading state to notify the UI.
- Presentation logic holders (Bloc/Cubit) must communicate with the Domain layer through UseCases, never Repositories directly.
- FetchFeaturedBooksUseCase is passed into the Cubit via constructor injection.
- Executing the call() method on the UseCase triggers the logic and returns an Either<Failure, List<BookEntity>>.
- The fold method elegantly unpacks the Either result via two callbacks: one for the Left (failure) and one for the Right (success).
- The Left side of the Either fold emits a FeaturedBooksFailure state containing the parsed error message.
- The Right side of the Either fold emits a FeaturedBooksSuccess state containing the payload data (List<BookEntity>).
Code Snippets
import 'package:bloc/bloc.dart';
import 'package:bookly/features/home/domain/entities/book_entity.dart';
import 'package:bookly/features/home/domain/use_cases/fetch_featured_books_use_case.dart';
import 'package:meta/meta.dart';
part 'featured_books_state.dart';
class FeaturedBooksCubit extends Cubit<FeaturedBooksState> {
FeaturedBooksCubit(this.featuredBooksUseCase) : super(FeaturedBooksInitial());
final FetchFeaturedBooksUseCase featuredBooksUseCase;
Future<void> fetchFeaturedBooks() async {
emit(FeaturedBooksLoading());
var result = await featuredBooksUseCase.call();
result.fold((failure) {
emit(FeaturedBooksFailure(failure.message));
}, (books) {
emit(FeaturedBooksSuccess(books));
});
}
}Architecture Insight
The Cubit's job is to fold an `Either<Failure, T>` into a state. result.fold(onLeft: emit(Failure), onRight: emit(Success)) is the pattern — one line, no try/catch, no branching past the fold.
If the Cubit needs more logic than that — debouncing, retry, combining multiple use cases — that's fine; that's exactly what the presentation layer is for. But the per-call shape stays this clean.
My Notes
The core concept here is enforcing Clean Architecture boundaries at the presentation level. The Cubit remains completely agnostic to the data origin (whether it's an API, Hive, or mock data); its only job is to execute the UseCase and interpret the result into actionable UI states. The use of the dartz package's Either type shines in this layer. It provides a functional, deterministic way to handle expected errors without polluting the presentation layer with try/catch blocks. The .fold() method forces the developer to explicitly handle both the Failure branch and the Success branch.
- [Editor's note] The instructor uses featuredBooksUseCase.call(). In Dart, classes implementing a call method are "callable objects." You can omit .call and write await featuredBooksUseCase(), which is cleaner and more idiomatic.