arch-atlas

Featured list view

Presentation (Building the UI)

08:23

Summary

This lesson covers the implementation of the horizontal ListView for the featured books section. The primary focus is resolving the common "unbounded height" exception that occurs when nesting a horizontal ListView inside a Column. The instructor demonstrates why wrapping the list in an Expanded widget is not always the best solution, and instead uses a SizedBox with a dynamic height based on MediaQuery to properly constrain the list while matching the design. Finally, visual spacing is fine-tuned using Padding and class names are refactored for clarity.

Key Ideas

  • Placing a horizontal ListView directly inside a Column throws an unbounded height exception because both widgets defer to their children for size constraints in the cross-axis.
  • Wrapping a ListView in an Expanded widget gives it all remaining vertical space, which prevents the crash but may distort the UI if a specific maximum height is required by the design.
  • SizedBox can be used to strictly enforce a layout height on a ListView.
  • MediaQuery.of(context).size.height * 0.3 is used to ensure the list takes up exactly 30% of the screen height regardless of the device.
  • Padding is added to the individual items within the itemBuilder rather than the ListView itself to space out the elements evenly while maintaining scroll behavior.

Code Snippets

FeaturedBooksListView with constrained height and horizontal scrollingdart
class FeaturedBooksListView extends StatelessWidget {
  const FeaturedBooksListView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: MediaQuery.of(context).size.height * .3,
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemBuilder: (context, index) {
          return const Padding(
            padding: EdgeInsets.symmetric(horizontal: 8),
            child: FeaturedListViewItem(),
          );
        },
      ),
    );
  }
}
HomeViewBody integrating the featured list viewdart
class HomeViewBody extends StatelessWidget {
  const HomeViewBody({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Column(
      children: [
        CustomAppBar(),
        FeaturedBooksListView(),
      ],
    );
  }
}
Adjusting app bar padding for better visual alignment with the new list viewdart
class CustomAppBar extends StatelessWidget {
  const CustomAppBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(left: 24, right: 24, top: 40, bottom: 20),
      child: Row(
        children: [
          Image.asset(
            AssetsData.logo,
            height: 18,
          ),
          const Spacer(),
          IconButton(
            onPressed: () {},
            icon: const Icon(
              FontAwesomeIcons.magnifyingGlass,
              size: 24,
            ),
          )
        ],
      ),
    );
  }
}

Architecture Insight

Architecture insight

Right now this list is hardcoded. That's fine — but be aware: when you wire data later (section 5), the widget should still render the same way regardless of whether the data came from cache, network, or a stub. That invariance is the CA promise.

The widget's contract should be: "give me a List<Book>, I render it." Where the list came from is somebody else's problem (a Cubit + use case + repository).

Looking ahead — Section 5 replaces the hardcoded list with a Cubit + FetchFeaturedBooksUseCase pipeline.

My Notes

  • The Unbounded Height Dilemma: The video gives a practical look at the classic Flutter ListView inside a Column error. The instructor initially reaches for Expanded, which is the instinctual fix for many developers, but correctly pivots to SizedBox to maintain the design's intended aspect ratio.
  • [Editor's note] Missing `itemCount`: The ListView.builder in this lesson is technically infinite and will keep building items if you scroll endlessly. When hooking this up to a Bloc/Cubit state later, ensuring itemCount: state.books.length is critical to prevent crashes.
  • [Editor's note] `MediaQuery` vs `LayoutBuilder`: Hardcoding MediaQuery.of(context).size.height * .3 ties the presentation logic directly to screen size globally. While it works for a quick layout, Clean Architecture UI practices generally favor passing down constraints via LayoutBuilder so widgets remain isolated and testable without a full screen context.