Skip to content

Chapter 4: The Jank-inator - Optimizing Network Images ​

Of course. After optimizing the building of widgets in a list, we'll now optimize the content within them, specifically the thing that causes the most "jank": loading images from the network.


The Scenario πŸ“ ​

  • System: A shopping app that displays a list of products using ListView.builder. Each item in the list has a product image loaded from a URL.
  • Problem: When the user scrolls the list, the app "janks" or stutters every time a new image appears. The image also appears abruptly after it's finished loading, and if you scroll away and then back, the image has to be downloaded all over again.

The Problematic Code (Using Image.network) ​

The simplest approach is to use the built-in Image.network widget.

dart
ListView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) {
    final product = products[index];
    return Card(
      child: Column(
        children: [
          // MISTAKE: Using Image.network directly in a long list
          Image.network(product.imageUrl),
          Text(product.name),
        ],
      ),
    );
  },
);

The Bottleneck Analysis 🧐 ​

  1. No Caching: The Image.network widget is very basic. Every time it's built, it will always send a network request to download the image, even if that image has been downloaded before. If the user scrolls a widget off-screen and then back on, the widget is rebuilt, and the image is downloaded all over again.
  2. No Loading Indicator: While the image is being downloaded, the widget just shows an empty space. The user doesn't know what's happening.
  3. No Error Handling: If the image fails to load (e.g., weak network, broken URL), the user will just see a cryptic error.

The Solution: Use a Specialized Package like cached_network_image βœ… ​

  • The Logic: Instead of handling the complex logic of caching, showing loading indicators, and handling errors yourself, use a powerful community library that has already done it for you. cached_network_image is the most popular and best choice.

  • Optimized Code:

    1. Add the package to pubspec.yaml: cached_network_image: ^3.3.1
    2. Use the CachedNetworkImage widget:
    dart
    import 'package:cached_network_image/cached_network_image.dart';
    
    ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return Card(
          child: Column(
            children: [
              // OPTIMIZED: Use CachedNetworkImage
              CachedNetworkImage(
                imageUrl: product.imageUrl,
                // Show a spinner while waiting
                placeholder: (context, url) => Center(child: CircularProgressIndicator()),
                // Show an error icon if it fails
                errorWidget: (context, url, error) => Icon(Icons.error),
                // Fade in the image for a smoother appearance
                fadeInDuration: const Duration(milliseconds: 300),
              ),
              Text(product.name),
            ],
          ),
        );
      },
    );

Analysis of the Result ✨ ​

  1. Smooth Scrolling: After the first download, images are saved to the cache (both in memory and on disk). When the user scrolls back, the image is loaded from the cache almost instantly, eliminating network latency and making scrolling much smoother.
  2. Better User Experience:
    • The placeholder provides immediate feedback, letting the user know the app is loading data.
    • The errorWidget handles error cases gracefully.
  3. Saves Network Data: The app doesn't have to re-download unnecessary images, which saves data for the user.

Conclusion:

  • The Golden Rule: "Never use Image.network directly in a long, scrollable list."
  • Always use a specialized package like cached_network_image to manage loading and caching network images. It provides the essential features needed to create a professional and high-performance application.