Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug report] SetState will Reset ExtendedImage and clear the Crop State #712

Open
Peng-Qian opened this issue Nov 22, 2024 · 7 comments
Open

Comments

@Peng-Qian
Copy link

Version

^9.0.7

Platforms

Android, iOS

Device Model

emulator, iPhone 15 Pro Plus

flutter info

[✓] Flutter (Channel stable, 3.24.4, on macOS 15.1.1 24B91 darwin-arm64, locale en-NZ)
    • Flutter version 3.24.4 on channel stable at /Users/qian/Developer/SDK/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 603104015d (4 weeks ago), 2024-10-24 08:01:25 -0700
    • Engine revision db49896cf2
    • Dart version 3.5.4
    • DevTools version 2.37.3

[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
    • Android SDK at /Users/qian/Library/Android/sdk
    • Platform android-35, build-tools 35.0.0
    • ANDROID_SDK_ROOT = /Users/qian/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.11+0-17.0.11b1207.24-11852314)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 16.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16B40
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2024.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.11+0-17.0.11b1207.24-11852314)

[✓] IntelliJ IDEA Community Edition (version 2024.1.4)
    • IntelliJ at /Users/qian/Applications/IntelliJ IDEA Community Edition.app
    • Flutter plugin version 82.1.2
    • Dart plugin version 241.18808

[✓] VS Code (version 1.77.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.62.0

[✓] Connected device (5 available)
    • iPhone 15 Q (mobile)            • 00008130-000C58C90E8B803A            • ios            • iOS 18.0.1 22A3370
    • iPhone 16 Pro Max (mobile)      • 292E7C90-95EA-4FA4-BFD8-7612F9E8D2E5 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-18-1 (simulator)
    • macOS (desktop)                 • macos                                • darwin-arm64   • macOS 15.1.1 24B91 darwin-arm64
    • Mac Designed for iPad (desktop) • mac-designed-for-ipad                • darwin         • macOS 15.1.1 24B91 darwin-arm64
    • Chrome (web)                    • chrome                               • web-javascript • Google Chrome 131.0.6778.86
    ! Error: Browsing on the local area network for 彭骞的Apple Watch. Ensure the device is unlocked and discoverable via Bluetooth. (code -27)

[✓] Network resources
    • All expected network resources are available.

• No issues found!

How to reproduce?

  1. run the example code
  2. crop image
  3. click float button which is just call setState()
  4. option: uncomment the dart editActionDetailsIsChanged part and the crop will not work at all since the bug

Logs

No response

Example code (optional)

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

final _key = GlobalKey();
final _editorKey = GlobalKey<ExtendedImageEditorState>();
final _gestureKey = GlobalKey<ExtendedImageGestureState>();

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _controller = ImageEditorController();
  String info = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme
            .of(context)
            .colorScheme
            .inversePrimary,
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          Text(info),
          Flexible(
            child: ExtendedImage.network(
              'https://gratisography.com/wp-content/uploads/2024/03/gratisography-funflower-1170x780.jpg',
              key: _key,
              extendedImageGestureKey: _gestureKey,
              extendedImageEditorKey: _editorKey,
              fit: BoxFit.contain,
              mode: ExtendedImageMode.editor,
              enableLoadState: true,
              enableMemoryCache: true,
              enableSlideOutPage: true,
              cache: true,
              imageCacheName: 'image_editor',
              borderRadius: BorderRadius.circular(33),
              clipBehavior: Clip.antiAlias,
              initEditorConfigHandler: (state) {
                return EditorConfig(
                  maxScale: 8.0,
                  cropRectPadding: const EdgeInsets.all(20),
                  hitTestSize: 20,
                  cropAspectRatio: 1.0,
                  controller: _controller,
                  initCropRectType: InitCropRectType.imageRect,
                  cornerColor: Colors.white,
                  lineColor: Colors.white,
                  // editActionDetailsIsChanged: (details) {
                  //   setState(() {
                  //     info = 'rotateDegrees: ${_controller.rotateDegrees}\n'
                  //         'cropAspectRatio: ${_controller.cropAspectRatio}\n'
                  //         'cropRect: ${_controller.editActionDetails?.cropRect}\n';
                  //   });
                  // },
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () =>
            setState(() {
              info = 'rotateDegrees: ${_controller.rotateDegrees}\n'
                  'cropAspectRatio: ${_controller.cropAspectRatio}\n'
                  'cropRect: ${_controller.editActionDetails?.cropRect}\n';
            }),
        tooltip: 'refresh',
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

Contact

No response

@Peng-Qian
Copy link
Author

This is quite critical since we cannot customize the editor and cannot fetch the crop's information.

@zmtzawqlp
Copy link
Member

as design , you should update state by controller

@zmtzawqlp
Copy link
Member

you should update only Text(info) state

@Peng-Qian
Copy link
Author

I believe the widget should not reset its state when the parent calls setState, as this design significantly limits user customization and its compatibility with other widgets. Requiring an isolated environment for the widget to function makes it inconvenient and less practical for broader use.

Is there a specific reason why this widget needs to unnecessarily reset its state?

@Peng-Qian
Copy link
Author

Just checked, and version 9.06 works fine with it. It seems there might be a minor bug introduced in the 9.07 update.

@Peng-Qian
Copy link
Author

Peng-Qian commented Nov 22, 2024

I quickly checked the commits, and this issue might be caused by the equality check in lib/src/editor/editor_config.dart:

@Override
int get hashCode {
  return Object.hash(
    maxScale,
    cropRectPadding,
    cornerSize,
    cornerColor,
    lineColor,
    lineHeight,
    editorMaskColorHandler,
    hitTestSize,
    animationDuration,
    tickerDuration,
    cropAspectRatio,
    initialCropAspectRatio,
    initCropRectType,
    cropLayerPainter,
    speed,
    hitTestBehavior,
    editActionDetailsIsChanged,
    reverseMousePointerScrollDirection,
    controller,
  );
}

The issue seems to be introduced in lib/src/editor/editor.dart:

if (oldConfig == config) {
  return;
}
_editorConfig = config;

Since the object is immutable, it always fails value equality checks, which forces the widget to reset everything.

I haven’t tested the code yet—this conclusion is based on a quick review of the commits. @zmtzawqlp

@zmtzawqlp
Copy link
Member

  @override
  void didUpdateWidget(ExtendedImageEditor oldWidget) {
    _editActionDetails = null;
    _initGestureConfig();
    super.didUpdateWidget(oldWidget);
  }

reset state when didUpdateWidget. make sure the state is clearing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants