Question #176MediumFlutter Basics

How to control the image widget taking so much ram → particularly when list of images shown example list of 1000 people profile like profile image with there names ?

#widget

Answer

Overview

Loading large lists of images can consume excessive RAM, causing app crashes or poor performance. The solution is to use image caching, lazy loading, and memory-efficient widgets.


Problem: High RAM Usage

Without optimization, loading 1000 profile images can use 500MB+ RAM.

dart
// ❌ Bad: Loads all images at once (high RAM usage)
ListView(
  children: List.generate(1000, (index) {
    return ListTile(
      leading: Image.network('https://example.com/profile$index.jpg'),
      title: Text('User $index'),
    );
  }),
)

Issues:

  • Loads all 1000 images simultaneously
  • No caching (re-downloads on scroll)
  • No memory management (images stay in RAM)

Solution 1: Use ListView.builder (Lazy Loading)

Only builds visible items, not all 1000 at once.

dart
// ✅ Good: Only loads visible items
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) {
    return ListTile(
      leading: Image.network('https://example.com/profile$index.jpg'),
      title: Text('User $index'),
    );
  },
)

Benefit: Only 10-20 items in memory at a time (visible + buffer).


Solution 2: Use cached_network_image (Disk Caching)

Caches images to disk, preventing re-downloads.

Installation

yaml
dependencies:
  cached_network_image: ^3.3.0

Usage

dart
import 'package:cached_network_image/cached_network_image.dart';

ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CachedNetworkImage(
        imageUrl: 'https://example.com/profile$index.jpg
        placeholder: (context, url) => CircularProgressIndicator(),
        errorWidget: (context, url, error) => Icon(Icons.error),
        width: 50,
        height: 50,
        fit: BoxFit.cover,
      ),
      title: Text('User $index'),
    );
  },
)

Benefits:

  • Downloads once, caches to disk
  • Shows cached image on subsequent loads
  • Placeholder while loading

Solution 3: Specify Image Dimensions (Reduce Memory)

Always specify

text
width
and
text
height
to avoid loading full-resolution images into RAM.

dart
// ❌ Bad: Loads full-size image (e.g., 3000x3000px)
Image.network('https://example.com/profile.jpg')

// ✅ Good: Decodes to 50x50px (saves RAM)
CachedNetworkImage(
  imageUrl: 'https://example.com/profile.jpg
  width: 50,
  height: 50,
  fit: BoxFit.cover,
)

Impact:

  • Full-size 3000x3000 image: ~36MB RAM per image
  • Resized 50x50 image: ~10KB RAM per image

Solution 4: Use Image.memory with cacheWidth/cacheHeight

For

text
Image.network()
without caching plugin.

dart
Image.network(
  'https://example.com/profile.jpg
  cacheWidth: 100,  // Decode to max 100px width
  cacheHeight: 100, // Decode to max 100px height
  fit: BoxFit.cover,
)

Benefit: Reduces memory usage by decoding smaller images.


Solution 5: Limit Cache Size

Configure

text
cached_network_image
to limit cache size.

dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';

void main() {
  // Configure cache manager
  final customCacheManager = CacheManager(
    Config(
      'customCacheKey',
      stalePeriod: Duration(days: 7),  // Keep for 7 days
      maxNrOfCacheObjects: 500,        // Max 500 images
      repo: JsonCacheInfoRepository(databaseName: 'customCache'),
    ),
  );

  runApp(MyApp());
}

// Use custom cache manager
CachedNetworkImage(
  imageUrl: 'https://example.com/profile.jpg
  cacheManager: customCacheManager,
)

Solution 6: Use CircleAvatar for Profile Pictures

Optimized widget for circular profile images.

dart
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(
        radius: 25,
        backgroundImage: CachedNetworkImageProvider(
          'https://example.com/profile$index.jpg
        ),
      ),
      title: Text('User $index'),
    );
  },
)

Solution 7: Dispose Images on Scroll

Use

text
AutomaticKeepAliveClientMixin
to dispose off-screen images.

dart
class ProfileTile extends StatefulWidget {
  final String imageUrl;
  final String name;

  ProfileTile({required this.imageUrl, required this.name});

  
  _ProfileTileState createState() => _ProfileTileState();
}

class _ProfileTileState extends State<ProfileTile>
    with AutomaticKeepAliveClientMixin {
  
  bool get wantKeepAlive => false; // Don't keep alive (dispose off-screen)

  
  Widget build(BuildContext context) {
    super.build(context); // Required for AutomaticKeepAliveClientMixin

    return ListTile(
      leading: CachedNetworkImage(
        imageUrl: widget.imageUrl,
        width: 50,
        height: 50,
        fit: BoxFit.cover,
      ),
      title: Text(widget.name),
    );
  }
}

Solution 8: Use Thumbnails from Server

Serve smaller thumbnail images instead of full-size images.

dart
// ❌ Bad: Load full 3000x3000 image
Image.network('https://example.com/profile/large.jpg')

// ✅ Good: Load 100x100 thumbnail
Image.network('https://example.com/profile/thumb.jpg')

Server-side:

text
https://example.com/profile/123/large.jpg   (3000x3000)
https://example.com/profile/123/thumb.jpg   (100x100)

Complete Example

dart
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';

class ProfileListScreen extends StatelessWidget {
  final List<User> users = List.generate(1000, (i) => User(
    id: i,
    name: 'User $i',
    imageUrl: 'https://picsum.photos/200?random=$i
  ));

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('1000 Profiles')),
      body: ListView.builder(
        itemCount: users.length,
        itemBuilder: (context, index) {
          final user = users[index];

          return ListTile(
            leading: CircleAvatar(
              radius: 25,
              backgroundImage: CachedNetworkImageProvider(
                user.imageUrl,
              ),
            ),
            title: Text(user.name),
            subtitle: Text('ID: ${user.id}'),
          );
        },
      ),
    );
  }
}

class User {
  final int id;
  final String name;
  final String imageUrl;

  User({required this.id, required this.name, required this.imageUrl});
}

Memory Profiling

Use Flutter DevTools to monitor memory usage.

bash
flutter run --profile
# Open DevTools → Memory tab

Check:

  • Memory usage before/after optimizations
  • Number of images in memory
  • Cache hit rate

Comparison: Optimization Impact

ApproachRAM (1000 images)Performance
No optimization~1GB+❌ Crashes
ListView.builder~100MB⚠️ Moderate
+ cached_network_image~50MB✅ Good
+ Specify dimensions~20MB✅ Great
+ Server thumbnails~10MB✅ Excellent

Best Practices

dart
// ✅ Use ListView.builder (lazy loading)
ListView.builder(itemCount: 1000, itemBuilder: ...)

// ✅ Use cached_network_image (disk caching)
CachedNetworkImage(imageUrl: '...')

// ✅ Specify image dimensions
CachedNetworkImage(width: 50, height: 50, ...)

// ✅ Use server-side thumbnails
imageUrl: 'https://example.com/thumb.jpg

// ❌ Don't load all images at once
// ListView(children: List.generate(1000, ...)) ❌

// ❌ Don't load full-size images for small widgets
// Image.network('https://example.com/large.jpg') ❌

Summary

SolutionRAM SavingsComplexity
ListView.builder~90%Easy
cached_network_image~50%Easy
Specify dimensions~60%Easy
Server thumbnails~80%Moderate (backend work)
Image disposal~20%Moderate

Key Takeaway: Combine ListView.builder + cached_network_image + specified dimensions for optimal performance.

Learn more: