arch-atlas

Pagination States part 2

Presentation (Integration)

11:48

Summary

This lesson focuses on improving user experience by handling pagination errors specifically within the Bloc state management pattern. Instead of replacing the entire UI with an error screen when fetching subsequent pages fails, the instructor demonstrates how to emit a FeaturedBooksPaginationFailure state while retaining the already loaded list. It covers updating the Cubit to emit this state conditionally based on the page number, handling the state in the BlocConsumer's listener to trigger a SnackBar, and extracting the SnackBar logic into a reusable helper method.

Key Ideas

  • FeaturedBooksPaginationFailure state implementation allows the UI to handle pagination errors distinctly from initial load errors.
  • Conditional emission in the Cubit ensures that full-screen errors only trigger when pageNumber is 0, while subsequent failures trigger a different state.
  • BlocConsumer enables separating the UI building logic from side-effect execution like showing a SnackBar.
  • ScaffoldMessenger.of(context).showSnackBar() is the correct way to trigger temporary error messages without causing UI rebuilds or navigation changes.
  • Side effects (like showing a dialog or snackbar) must be placed in the listener of a BlocConsumer or BlocListener, never in the builder.

Code Snippets

Adding the FeaturedBooksPaginationFailure state to featured_books_state.dartdart
class FeaturedBooksPaginationFailure extends FeaturedBooksState {
  final String errMessage;
  const FeaturedBooksPaginationFailure(this.errMessage);
}
Updating fetchFeaturedBooks in featured_books_cubit.dart to emit the pagination error statedart
Future<void> fetchFeaturedBooks({int pageNumber = 0}) async {
  if (pageNumber == 0) {
    emit(FeaturedBooksLoading());
  } else {
    emit(FeaturedBooksPaginationLoading());
  }
  
  var result = await fetchFeaturedBooksUseCase.call(pageNumber);
  
  result.fold((failure) {
    if (pageNumber == 0) {
      emit(FeaturedBooksFailure(failure.message));
    } else {
      emit(FeaturedBooksPaginationFailure(failure.message));
    }
  }, (books) {
    emit(FeaturedBooksSuccess(books));
  });
}
Handling the pagination failure state in the BlocConsumer listenerdart
BlocConsumer<FeaturedBooksCubit, FeaturedBooksState>(
  listener: (context, state) {
    if (state is FeaturedBooksPaginationFailure) {
      ScaffoldMessenger.of(context).showSnackBar(
        buildErrorSnackBar(state.errMessage),
      );
    }
  },
  builder: (context, state) {
    if (state is FeaturedBooksSuccess ||
        state is FeaturedBooksPaginationLoading ||
        state is FeaturedBooksPaginationFailure) {
      return FeaturedBooksListView(books: books);
    } else if (state is FeaturedBooksFailure) {
      return Text(state.errMessage);
    } else {
      return const CircularProgressIndicator();
    }
  },
)
The extracted helper method for creating the snackbardart
SnackBar buildErrorSnackBar(String errMessage) {
  return SnackBar(
    backgroundColor: Colors.red,
    content: Text(
      errMessage,
      style: const TextStyle(color: Colors.white),
    ),
    duration: const Duration(seconds: 3),
  );
}

My Notes

  • Separation of Concerns: The core takeaway is avoiding "one-size-fits-all" error handling. By creating a FeaturedBooksPaginationFailure state, you differentiate between a fatal error (the app couldn't load anything) and a non-fatal error (couldn't load the next page).
  • The Listener/Builder Rule: This lesson provides a classic example of why BlocConsumer is necessary. Beginners often mistakenly try to show SnackBars inside the builder function. [Editor's note: Always remember that builder should be pure and focused only on returning the UI state; listener is for "one-off" events that respond to state changes, like navigation or snackbars.]
  • Refactoring: The instructor spent time fixing the widget extraction. It is good practice to put these UI helpers in a separate utils or functions folder if they are used across different pages, though keeping them in the same file is fine while the app is small.