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

hexInputController causes setState() called after dispose() exception #61

Closed
glaceon2000 opened this issue Nov 3, 2021 · 11 comments
Closed
Labels
bug Something isn't working

Comments

@glaceon2000
Copy link

The code that throws this exception.
This exception is thrown when I close the Dialog and open it again, and is not thrown once I remove the controller.

class ColorPickerTextField extends StatefulWidget {
  const ColorPickerTextField(
      {Key? key, required this.color, this.onColorChanged})
      : super(key: key);
  final Color color;
  final void Function(String)? onColorChanged;

  @override
  _ColorPickerTextFieldState createState() => _ColorPickerTextFieldState();
}

class _ColorPickerTextFieldState extends State<ColorPickerTextField> {
  late Color _color;
  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _color = widget.color;
    _controller.text = _color.value.toRadixString(16).toUpperCase();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
        fillColor: _color,
        hoverColor: _color,
        readOnly: true,
        textColor:
            (_color.red * 0.299 + _color.green * 0.587 + _color.blue * 0.114) >
                    186
                ? ColorProvider.kDarkBlue
                : Colors.white,
        controller: _controller,
        onTap: () => showDialog(
              context: context,
              builder: (_) => AlertDialog(
                title: Text(
                  'Pick a color!',
                ),
                content: SingleChildScrollView(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      ColorPicker(
                        displayThumbColor: true,
                        hexInputController: _controller,
                        pickerColor: _color,
                        onColorChanged: (color) {
                          setState(() {
                            _color = color;
                          });
                        },
                        showLabel: true,
                        pickerAreaHeightPercent: 0.8,
                      ),
                      Container(
                        width: 300,
                        padding: const EdgeInsets.all(16),
                        child: TextFormField(
                          controller: _controller,
                          autofocus: true,
                          ),
                          maxLength: 8,
                          inputFormatters: [
                            UpperCaseTextFormatter(),
                            FilteringTextInputFormatter.allow(
                              RegExp(kValidHexPattern),
                            ),
                          ],
                        ),
                      )
                    ],
                  ),
                ),
                actions: <Widget>[
                  CustomOrangeTextButton(
                    onTap: () => Navigator.of(context).pop(),
                    text: 'Done',
                  ),
                ],
              ),
            ));
  }
}

class UpperCaseTextFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(oldValue, TextEditingValue newValue) =>
      TextEditingValue(
          text: newValue.text.toUpperCase(), selection: newValue.selection);
}
@mchome
Copy link
Owner

mchome commented Nov 8, 2021

I don't have any issues with your code above, is something I missed?

@glaceon2000
Copy link
Author

@mchome Did you try opening, then close then open the Dialog again? The exception is caught when I open the dialog again. It doesn't throw this exception once I remove the hexInputController value so I reckon it's something internal with the ColorPicker widget.

The following assertion was thrown while dispatching notifications for TextEditingController:
setState() called after dispose(): _ColorPickerState#a6d2e(lifecycle state: defunct, not mounted)

This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.

The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

@mchome
Copy link
Owner

mchome commented Nov 8, 2021

Please provide the full code & steps that I can reproduce the bug.

@glaceon2000
Copy link
Author

@mchome
Steps:

  1. Click on text field to open dialog
  2. Click on 'Done'
  3. Click on text field to open dialog again
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(title: const Text(_title)),
        body: MyStatefulWidget(),
      ),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          height: 196,
          child: ColorPickerTextField(
            color: Colors.red,
          ),
        ),
      ),
    );
  }
}

class ColorPickerTextField extends StatefulWidget {
  const ColorPickerTextField(
      {Key? key, required this.color, this.onColorChanged})
      : super(key: key);
  final Color color;
  final void Function(String)? onColorChanged;

  @override
  _ColorPickerTextFieldState createState() => _ColorPickerTextFieldState();
}

class _ColorPickerTextFieldState extends State<ColorPickerTextField> {
  late Color _color;
  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    _color = widget.color;
    _controller.text = _color.value.toRadixString(16).toUpperCase();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
        controller: _controller,
        onTap: () => showDialog(
              context: context,
              builder: (_) => AlertDialog(
                title: Text(
                  'Pick a color!',
                ),
                content: SingleChildScrollView(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      ColorPicker(
                        displayThumbColor: true,
                        hexInputController: _controller,
                        pickerColor: _color,
                        onColorChanged: (color) {
                          setState(() {
                            _color = color;
                          });
                        },
                        showLabel: true,
                        pickerAreaHeightPercent: 0.8,
                      ),
                      Container(
                        width: 300,
                        padding: const EdgeInsets.all(16),
                        child: TextFormField(
                          controller: _controller,
                          autofocus: true,
                          readOnly: true,
                          maxLength: 8,
                          inputFormatters: [
                            UpperCaseTextFormatter(),
                            FilteringTextInputFormatter.allow(
                              RegExp(kValidHexPattern),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
                actions: <Widget>[
                  TextButton(
                    onPressed: () => Navigator.of(context).pop(),
                    child: Text('Done'),
                  ),
                ],
              ),
            ));
  }
}

class UpperCaseTextFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(oldValue, TextEditingValue newValue) =>
      TextEditingValue(
          text: newValue.text.toUpperCase(), selection: newValue.selection);
}

@mchome
Copy link
Owner

mchome commented Nov 8, 2021

Nan, here's my log:

Restarted application in 1,158ms.
D/InputMethodManager( 9702): showSoftInput() view=io.flutter.embedding.android.FlutterView{9712b15 VFED..... .F...... 0,0-1080,2028 #1 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
W/IInputConnectionWrapper( 9702): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): endBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): endBatchEdit on inactive InputConnection
D/InsetsController( 9702): show(ime(), fromIme=true)
D/InsetsController( 9702): show(ime(), fromIme=true)
D/InputMethodManager( 9702): showSoftInput() view=io.flutter.embedding.android.FlutterView{9712b15 VFED..... .F...... 0,0-1080,2028 #1 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
W/IInputConnectionWrapper( 9702): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): getTextBeforeCursor on inactive InputConnection
W/IInputConnectionWrapper( 9702): getTextAfterCursor on inactive InputConnection
W/IInputConnectionWrapper( 9702): getSelectedText on inactive InputConnection
W/IInputConnectionWrapper( 9702): endBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): getTextAfterCursor on inactive InputConnection
W/IInputConnectionWrapper( 9702): endBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper( 9702): getTextAfterCursor on inactive InputConnection
W/IInputConnectionWrapper( 9702): endBatchEdit on inactive InputConnection
D/InsetsController( 9702): show(ime(), fromIme=true)
D/InsetsController( 9702): show(ime(), fromIme=true)
D/InputMethodManager( 9702): showSoftInput() view=io.flutter.embedding.android.FlutterView{9712b15 VFED..... .F...... 0,0-1080,2028 #1 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
D/InsetsController( 9702): show(ime(), fromIme=true)
D/InputMethodManager( 9702): showSoftInput() view=io.flutter.embedding.android.FlutterView{9712b15 VFED..... .F...... 0,0-1080,2028 #1 aid=1073741824} flags=0 reason=SHOW_SOFT_INPUT
D/InsetsController( 9702): show(ime(), fromIme=true)

Device: Pixel 3 (Android 12)
Flutter: 2.6.0-11.0.pre

@glaceon2000
Copy link
Author

I'm currently using it on Flutter Web - Chrome. I didn't expect this to be a platform issue

@mchome
Copy link
Owner

mchome commented Nov 8, 2021

Oh, maybe I am writing v1.0.0, I will try v0.6.0 again.

@glaceon2000
Copy link
Author

I actually just tested on Pixel 4 XL (Android 11) - Flutter 2.5.2 stable and it produced the same exception. Would you kindly check again

@mchome mchome added the bug Something isn't working label Nov 8, 2021
@mchome
Copy link
Owner

mchome commented Nov 8, 2021

I post v0.6.1 with the fixes.

@mchome mchome closed this as completed Nov 9, 2021
@glaceon2000
Copy link
Author

glaceon2000 commented Nov 9, 2021

May I ask what led to the exception and how you handled it?

@mchome
Copy link
Owner

mchome commented Nov 9, 2021

Just remove the listener of textController in dispose() of ColorPicker.

colorpicker.dart:
class ColorPicker extends StatefulWidget {
...
  @override
  void dispose() {
    widget.hexInputController?.removeListener(colorPickerTextInputListener);
    super.dispose();
  }
...

flutter_colorpicker-v0.6.1.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants