Baseflow / Baseflow/octo_image
Unnecessary repaint triggered due to unequal `ResizeImage.resizeIfNeeded` instances
- 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
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