Skip to content

Unnecessary repaint triggered due to unequal ResizeImage.resizeIfNeeded instancesΒ #39

@dhyash-simform

Description

@dhyash-simform

πŸ› Bug Report

When using the parameters memCacheWidth or memCacheHeight in the OctoImage widget, it causes unnecessary repaints on rebuilds β€” even when the image configuration hasn't changed.

Root Cause

The issue arises because ResizeImage.resizeIfNeeded creates a new ResizeImage instance every time, and that class does not override equality (==). So this condition in the widget always evaluates to true, even with identical inputs:

Relevant code in lib/src/image/image.dart:

// ...
image = ResizeImage.resizeIfNeeded(
          memCacheWidth,
          memCacheHeight,
          image,
        )
// ...

@override
  void didUpdateWidget(OctoImage oldWidget) {
    // ....
    if (oldWidget.image != widget.image) {

    }
    // ...
  }

As a result, even if the image, memCacheWidth, and memCacheHeight values are the same, the widget incorrectly treats the image as changed and repaints, which negatively impacts performance.

This issue stems from Flutter’s image caching system. I have already reported it here:

πŸ”— flutter/flutter#172642

Note: This behavior only occurs when memCacheWidth or memCacheHeight is provided.
If both are null, ResizeImage.resizeIfNeeded returns the original image provider and equality works as expected.

Expected behavior

If image, memCacheWidth, and memCacheHeight values are unchanged, the widget should not trigger a repaint.

Reproduction steps

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

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  static const NetworkImage flutterLogoUrl = NetworkImage(
    'https://storage.googleapis.com/cms-storage-bucket/lockup_flutter_vertical.a9d6ce81aee44ae017ee.png',
  );

  ImageProvider provider = flutterLogoUrl;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() {
          provider = flutterLogoUrl;
        }),
      ),
      body: Center(
        child: RepaintBoundary(
          child: OctoImage(
            image: provider,
            memCacheWidth: 300,
            memCacheHeight: 400,
          ),
        ),
      ),
    );
  }
}
  • Tap the floating action button β€” setState reassigns the same image, but OctoImage repaints due to failed equality.

Configuration

Version: 2.1.0

Platform:

  • πŸ“± iOS
  • πŸ€– Android

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions