Skip to content

Chapter 3: Taming Long Lists - ListView vs. ListView.builder

Of course. After optimizing individual widgets, we'll tackle one of the most common bottlenecks in Flutter: displaying a long list.


The Scenario 📝

  • System: A news application that needs to display a list of 500 articles.
  • Problem: When the user opens the list screen, the app "freezes" for a long time before displaying anything. The application's memory (RAM) usage spikes.

The Problematic Code (Building the Entire List)

The most intuitive approach is to create a List<Widget> and pass it to a ListView.

dart
// Assume `allArticles` is a List containing 500 Article objects
class ArticleListScreen extends StatelessWidget {
  final List<Article> allArticles;

  ArticleListScreen({required this.allArticles});

  @override
  Widget build(BuildContext context) {
    return ListView(
      // MISTAKE: .map().toList() creates all 500 widgets at once
      children: allArticles.map((article) {
        return ArticleListItem(article: article);
      }).toList(),
    );
  }
}

The Bottleneck Analysis 🧐

  1. Build All at Once: The default ListView constructor requires you to provide a complete List<Widget>. This means that before the screen can be displayed, Flutter must run the map function and create all 500 ArticleListItem widgets and load them into memory.
  2. The Consequences:
    • Very Slow Initial Load Time: The app will hang while it's busy building 500 widgets.
    • High Memory Consumption: All 500 widgets, whether they are visible on the screen or not, occupy RAM.
    • Poor User Experience: The user has to wait and might think the app has crashed.

The Solution: Use ListView.builder for "Lazy" Building ✅

  • The Logic: ListView.builder is another constructor for ListView, specifically designed for long lists. It does not build all the widgets at once. Instead, it only builds the widgets that are currently or about to be visible on the screen.

  • Optimized Code:

    dart
    class ArticleListScreen extends StatelessWidget {
      final List<Article> allArticles;
    
      ArticleListScreen({required this.allArticles});
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: allArticles.length, // Provide the total number of items
          itemBuilder: (context, index) {
            // This function is only called for visible items
            // For example, it might only be called 10 times for the first 10 items
            print("Building item $index");
            final article = allArticles[index];
            return ArticleListItem(article: article);
          },
        );
      }
    }

Analysis of the Result ✨

  1. Instant Initial Load: The screen displays almost immediately because Flutter only needs to build the first 10-15 widgets to fill the screen.
  2. Memory Efficiency: Only the widgets currently on screen are kept in memory. As the user scrolls, old widgets that scroll off-screen are destroyed, and new ones are built.
  3. Smooth Scrolling: Because the framework only has to manage a small number of widgets at any given time, scrolling performance is very high.

Conclusion:

  • The Golden Rule: Always use ListView.builder (or similar builders like GridView.builder, CustomScrollView) when you have a long list or a list of unknown length.
  • The default ListView constructor should only be used for very short lists where all items can be displayed on the screen at once (e.g., a settings menu).