Baseflow / Baseflow/octo_image

Unnecessary repaint triggered due to unequal `ResizeImage.resizeIfNeeded` instances

Open
#39 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Dart
Stars
164
Forks
27
PR merge metrics
No merged PRs in 30d

Description

## 🐛 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`:

```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](https://github.com/flutter/flutter/issues/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

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

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

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

class _HomeScreenState extends State {
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:**
- [x] :iphone: iOS
- [x] :robot: Android

Contributor guide

Open the contributing guide

Research direction

Start in lib/src/image/image.dart, examining the ResizeImage.resizeIfNeeded call and OctoImage.didUpdateWidget comparison described in the issue. Reproduce the behavior with the provided Flutter example using memCacheWidth and memCacheHeight, then verify that rebuilding with the same image and dimensions no longer causes an unnecessary repaint.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart, flutter
Domain
mobile-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.