LogoSkills

flutter-inspector-image

Image debugging specialist. Use for cache analysis and memory usage inspection

항ëĒŠë‚´ėšŠ
ToolsRead, Glob, Grep
Modelhaiku
Skillsflutter-inspector

Flutter Inspector - Image Agent#

A specialized agent for analyzing image cache and memory usage at runtime.

Triggers#

Automatically activated when @flutter-inspector-image is invoked or the following keywords are detected:

  • Image cache, memory
  • Image loading, optimization
  • Cache size, clear

MCP Tools#

img_get_cache_stats#

Returns image cache statistics.

{
   " name " :  " img_get_cache_stats " ,
   " description " :  " Image cache statistics " ,
   " inputSchema " : {
     " type " :  " object " ,
     " properties " : {}
  }
}

Response example:

{
   " cache " : {
     " currentSize " : 52428800,
     " currentSizeFormatted " :  " 50 MB " ,
     " maximumSize " : 104857600,
     " maximumSizeFormatted " :  " 100 MB " ,
     " usagePercent " : 50,
     " liveImageCount " : 25,
     " pendingImageCount " : 3
  },
   " memory " : {
     " currentUsage " : 157286400,
     " currentUsageFormatted " :  " 150 MB " ,
     " peakUsage " : 209715200,
     " peakUsageFormatted " :  " 200 MB " 
   },
   " network " : {
     " cachedImages " : 120,
     " totalDownloaded " : 314572800,
     " totalDownloadedFormatted " :  " 300 MB " 
   }
}

img_analyze_warnings#

Analyzes image-related warnings and issues.

{
   " name " :  " img_analyze_warnings " ,
   " description " :  " Image warning analysis " ,
   " inputSchema " : {
     " type " :  " object " ,
     " properties " : {
       " threshold " : {
         " type " :  " integer " ,
         " description " :  " Warning threshold (MB) " ,
         " default " : 5
      }
    }
  }
}

Response example:

{
   " warnings " : [
    {
       " type " :  " oversized " ,
       " severity " :  " high " ,
       " message " :  " Image is 4x larger than display size " ,
       " image " :  " banner_home.png " ,
       " details " : {
         " displaySize " :  " 390x200 " ,
         " actualSize " :  " 1560x800 " ,
         " wastedMemory " :  " 4.7 MB " 
       },
       " suggestion " :  " Recommend using cacheWidth/cacheHeight " 
     },
    {
       " type " :  " memory " ,
       " severity " :  " medium " ,
       " message " :  " Cache usage exceeds 75% " ,
       " details " : {
         " usage " :  " 75 MB / 100 MB " 
       },
       " suggestion " :  " Recommend clearing old cache " 
     },
    {
       " type " :  " duplicate " ,
       " severity " :  " low " ,
       " message " :  " Same image cached at different sizes " ,
       " image " :  " profile_user123.jpg " ,
       " details " : {
         " variants " : [ " 100x100 " ,  " 200x200 " ,  " 400x400 " ]
      },
       " suggestion " :  " Recommend using consistent sizes " 
     }
  ],
   " score " : 65,
   " grade " :  " C " 
 }

img_clear_cache#

Clears the image cache.

{
   " name " :  " img_clear_cache " ,
   " description " :  " Clear image cache " ,
   " inputSchema " : {
     " type " :  " object " ,
     " properties " : {
       " type " : {
         " type " :  " string " ,
         " description " :  " Clear type " ,
         " enum " : [ " all " ,  " memory " ,  " disk " ,  " expired " ],
         " default " :  " expired " 
       }
    }
  }
}

Response example:

{
   " cleared " : {
     " type " :  " expired " ,
     " freedMemory " : 31457280,
     " freedMemoryFormatted " :  " 30 MB " ,
     " removedImages " : 45
  },
   " after " : {
     " currentSize " : 20971520,
     " currentSizeFormatted " :  " 20 MB " ,
     " liveImageCount " : 15
  }
}

App Integration Code#

// lib/debug/mcp_image_tools.dart
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'package:flutter/widgets.dart';
import 'package:cached_network_image/cached_network_image.dart';

class ImageAnalyzer {
  static final instance = ImageAnalyzer._();
  ImageAnalyzer._();

  final List<Map<String, dynamic>> _loadedImages = [];

  void trackImage({
    required String url,
    required Size displaySize,
    required Size actualSize,
    required int bytes,
  }) {
    _loadedImages.add({
      'url': url,
      'displaySize': displaySize,
      'actualSize': actualSize,
      'bytes': bytes,
      'loadedAt': DateTime.now(),
    });
  }

  Map<String, dynamic> getCacheStats() {
    final imageCache = PaintingBinding.instance.imageCache;
    return {
      'cache': {
        'currentSize': imageCache.currentSize,
        'currentSizeFormatted': _formatBytes(imageCache.currentSize),
        'maximumSize': imageCache.maximumSize,
        'maximumSizeFormatted': _formatBytes(imageCache.maximumSize),
        'usagePercent': (imageCache.currentSize / imageCache.maximumSize * 100).round(),
        'liveImageCount': imageCache.liveImageCount,
        'pendingImageCount': imageCache.pendingImageCount,
      },
    };
  }

  List<Map<String, dynamic>> analyzeWarnings({int threshold = 5}) {
    final warnings = <Map<String, dynamic>>[];
    final thresholdBytes = threshold * 1024 * 1024;

    for (final img in _loadedImages) {
      final displaySize = img['displaySize'] as Size;
      final actualSize = img['actualSize'] as Size;

      // Oversize check
      if (actualSize.width > displaySize.width * 2 ||
          actualSize.height > displaySize.height * 2) {
        warnings.add({
          'type': 'oversized',
          'severity': 'high',
          'message': 'Image is larger than display size',
          'image': img['url'],
          'details': {
            'displaySize': '${displaySize.width.toInt()}x${displaySize.height.toInt()}',
            'actualSize': '${actualSize.width.toInt()}x${actualSize.height.toInt()}',
          },
          'suggestion': 'Recommend using cacheWidth/cacheHeight',
        });
      }
    }

    // Cache usage check
    final cache = PaintingBinding.instance.imageCache;
    if (cache.currentSize > cache.maximumSize * 0.75) {
      warnings.add({
        'type': 'memory',
        'severity': 'medium',
        'message': 'Cache usage exceeds 75%',
        'suggestion': 'Recommend clearing old cache',
      });
    }

    return warnings;
  }

  Map<String, dynamic> clearCache(String type) {
    final cache = PaintingBinding.instance.imageCache;
    final beforeSize = cache.currentSize;

    switch (type) {
      case 'all':
      case 'memory':
        cache.clear();
        break;
      case 'expired':
        cache.clearLiveImages();
        break;
    }

    return {
      'cleared': {
        'type': type,
        'freedMemory': beforeSize - cache.currentSize,
        'freedMemoryFormatted': _formatBytes(beforeSize - cache.currentSize),
      },
      'after': {
        'currentSize': cache.currentSize,
        'currentSizeFormatted': _formatBytes(cache.currentSize),
      },
    };
  }

  String _formatBytes(int bytes) {
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
    if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
    return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
  }
}

void registerImageTools() {
  if (!kDebugMode) return;

  addMcpTool(MCPCallEntry.tool(
    handler: (_) => MCPCallResult(
      message: 'Cache stats',
      parameters: ImageAnalyzer.instance.getCacheStats(),
    ),
    definition: MCPToolDefinition(
      name: 'img_get_cache_stats',
      description: 'Image cache statistics',
      inputSchema: {'type': 'object', 'properties': {}},
    ),
  ));

  addMcpTool(MCPCallEntry.tool(
    handler: (params) {
      final threshold = params['threshold'] as int? ?? 5;
      return MCPCallResult(
        message: 'Image warnings',
        parameters: {
          'warnings': ImageAnalyzer.instance.analyzeWarnings(threshold: threshold),
        },
      );
    },
    definition: MCPToolDefinition(
      name: 'img_analyze_warnings',
      description: 'Image warning analysis',
      inputSchema: {
        'type': 'object',
        'properties': {
          'threshold': {'type': 'integer', 'default': 5},
        },
      },
    ),
  ));

  addMcpTool(MCPCallEntry.tool(
    handler: (params) {
      final type = params['type'] as String? ?? 'expired';
      return MCPCallResult(
        message: 'Cache cleared',
        parameters: ImageAnalyzer.instance.clearCache(type),
      );
    },
    definition: MCPToolDefinition(
      name: 'img_clear_cache',
      description: 'Clear image cache',
      inputSchema: {
        'type': 'object',
        'properties': {
          'type': {
            'type': 'string',
            'enum': ['all', 'memory', 'disk', 'expired'],
            'default': 'expired',
          },
        },
      },
    ),
  ));
}

Usage Examples#

Check cache status#

Q: Show me the image cache status
A: Run img_get_cache_stats
   - >   50 MB / 100 MB (50%), 25 images

Analyze image issues#

Q: Are there any image optimization issues?
A: Run img_analyze_warnings
   - >   3 warnings: 2 oversized, 1 memory, grade C

Clear cache#

Q: Clear the image cache
A: Run img_clear_cache type= " expired " 
    - >   30 MB freed, 45 images removed

Common Problem Diagnosis#

Memory warning#

1. Check current usage with img_get_cache_stats
2. Identify problematic images with img_analyze_warnings
3. Clear cache with img_clear_cache
4. Consider applying cacheWidth/cacheHeight

Slow image loading#

1. Check cache hit rate with img_get_cache_stats
2. Check network image downloads (@flutter-inspector-network)
3. Review image size optimization

App crash (OOM)#

1. Check peak memory with img_get_cache_stats
2. Find oversized images with img_analyze_warnings
3. Apply large image optimization
4. Consider adjusting maximum cache size

Optimization Recommendations#

Image Loading Patterns#

// CORRECT: Specify cache size
Image.network(
  imageUrl,
  cacheWidth: 200,
  cacheHeight: 200,
)

// CORRECT: Use CachedNetworkImage
CachedNetworkImage(
  imageUrl: imageUrl,
  memCacheWidth: 200,
  placeholder: (context, url) => CircularProgressIndicator(),
)

// WRONG: Loading at original size
Image.network(imageUrl)  // 4000x4000 image displayed at 100x100

Cache Settings#

// main.dart
void main() {
  // Cache size configuration
  PaintingBinding.instance.imageCache.maximumSize = 100 * 1024 * 1024; // 100 MB
  PaintingBinding.instance.imageCache.maximumSizeBytes = 200 * 1024 * 1024; // 200 MB

  runApp(const MyApp());
}
  • @flutter-inspector: Master inspector
  • @flutter-image-optimizer: Image optimization specialist agent
  • @flutter-inspector-log: Image loading related logs