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

QuillEditor freezes when adding content or giving a spacebar #1837

Open
1 task done
softwaredementor opened this issue Apr 27, 2024 · 2 comments
Open
1 task done

QuillEditor freezes when adding content or giving a spacebar #1837

softwaredementor opened this issue Apr 27, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@softwaredementor
Copy link

softwaredementor commented Apr 27, 2024

Is there an existing issue for this?

Flutter Quill version

9.3.6, 9.3.7, 9.3.8, 9.3.9

No response

Steps to reproduce

Tested on Edge, Chrome and Flutter WebApp version of the app

Step 1: Reference below code to create a form with single toolbar and multiple QuillEditor widgets.

Step 2: When I click on "+Add more details" button the first row is added. I click on the quilleditor, get the focus and I can type some content in it.
This is ok

Step 3: When I click on "+Add more details" again to add second row. I click on the second added QuillEditor, get the focus and can see the same content of first quilleditor (because first and second quill editors share the same quillcontroller).
I can see the print command of print("add more pressed");
This is also ok

Step 4: But when I type a space button in the second QuillEditor, my whole application freezes. I cannot type in the second Quilleditor at all nor interact with any of my widget.

I checked in ver 9.3.6, 09.3.7, 9.3.8 all seem to have same issues. Some isse related to Quilleditor focus or edit exists and user is unable to type

Expected results

  1. I should be able to type in the QuillEditor text when the focus is on without any issues.
  2. I should be able to interact with my web app widgets

Actual results

  1. Not able to any content in the second QuillEditor. Rarely I am able to type in second QuillEditor and add widgets (reproducible steps not identified for successful steps yet)
  2. I am not able to interact with my web app widgets as the whole application freezes with no errors or exceptions

Code sample

Code sample
import 'package:admin/models/Resume.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:provider/provider.dart';

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

  @override
  State<DynamicAdditionWidget> createState() => _DynamicAdditionWidgetState();
}

class _DynamicAdditionWidgetState extends State<DynamicAdditionWidget> {
  String sectionName = "Information";
  List<Widget> widgets = [];
  List<Widget> editorHeader = [];
  final List<QuillController> _quillControllers = [QuillController.basic()];
  ScrollController _scrollController = ScrollController();
  @override
  void initState() {
    super.initState();
    // TODO: implement initState
  }

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    sectionName = Provider.of<Resume>(context).ActiveSection;
    editorHeader = [];
    editorHeader.add(
      HeaderWidget(
        quillControllers: _quillControllers,
        sectionName: sectionName,
      ),
    );
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: 1,
      child: Container(
        decoration: BoxDecoration(
          border: Border.all(
            color: Color.fromARGB(255, 60, 67, 72), // Border color
            width: 1.0, // Border width
          ),
        ),
        child: Container(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              ...editorHeader,
              Flexible(
                child: Scrollbar(
                  thumbVisibility: true,
                  controller: _scrollController,
                  child: ReorderableListView.builder(
                    onReorder: (oldIndex, newIndex) {
                      setState(() {
                        if (newIndex > oldIndex) {
                          newIndex = newIndex - 1;
                        }
                        final item = widgets.removeAt(oldIndex);
                        widgets.insert(newIndex, item);
                      });
                    },
                    scrollController: _scrollController,
                    itemCount: widgets.length,
                    itemBuilder: (context, index) {
                      return Column(
                        key: ValueKey(widgets[
                            index]), // Use a ValueKey to ensure correct reordering
                        children: [
                          Container(
                            padding: EdgeInsets.only(
                              left: 12,
                              top: 12,
                            ),
                            child: Row(
                              children: [
                                // Your content here
                                widgets[index],
                                Padding(
                                  padding: const EdgeInsets.only(top: 24.0),
                                  child: Column(
                                    mainAxisAlignment: MainAxisAlignment.end,
                                    children: [
                                      IconButton(
                                        icon: Icon(
                                          Icons.close_sharp,
                                          size: 15,
                                          color: const Color.fromARGB(
                                              255, 214, 95, 107),
                                        ),
                                        onPressed: () {
                                          setState(() {
                                            widgets.removeAt(index);
                                          });
                                        },
                                      ),
                                    ],
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      );
                    },
                  ),
                ),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Expanded(
                    flex: 11,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        ElevatedButton(
                          child: Text(
                            "+ Add more details",
                            style: TextStyle(
                              color: Theme.of(context).primaryColor,
                            ),
                          ),
                          onPressed: () {
                            print("add more pressed");
                            setState(() {
                              widgets.add(
                                Expanded(
                                  child: Column(
                                    children: [
                                      Row(
                                        children: [
                                          Expanded(
                                            child: Container(
                                              decoration: BoxDecoration(
                                                border: Border.all(
                                                    color: Colors.grey[300]!),
                                                borderRadius:
                                                    BorderRadius.circular(2.0),
                                              ),
                                              child: QuillEditor.basic(
                                                configurations:
                                                    QuillEditorConfigurations(
                                                  placeholder:
                                                      "Compose your text here ...",
                                                  controller:
                                                      _quillControllers[0],
                                                  // autoFocus: true,
                                                  readOnly: false,
                                                  showCursor: true,
                                                  scrollable: true,
                                                  padding: EdgeInsets.symmetric(
                                                    vertical: 10.0,
                                                    horizontal: 8.0,
                                                  ),
                                                  sharedConfigurations:
                                                      const QuillSharedConfigurations(
                                                    locale: Locale('en'),
                                                  ),
                                                ),
                                              ),
                                            ),
                                          ),
                                        ],
                                      ),
                                    ],
                                  ),
                                ),
                              );
                            });
                          },
                        ),
                      ],
                    ),
                  ),
                  Expanded(
                    flex: 8,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: [
                        ElevatedButton(
                          onPressed: () {
                            // Add save functionality
                          },
                          child: Text("Save"),
                        ),
                        SizedBox(width: 8.0),
                        OutlinedButton(
                          onPressed: () {
                            // Add cancel functionality
                            _quillControllers[0].clear();
                          },
                          child: Text("Cancel"),
                        ),
                      ],
                    ),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

class HeaderWidget extends StatelessWidget {
  const HeaderWidget({
    super.key,
    required List<QuillController> quillControllers,
    required this.sectionName,
  }) : _quillControllers = quillControllers;

  final List<QuillController> _quillControllers;
  final String sectionName;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          decoration: BoxDecoration(
            border: Border(
              top: BorderSide(
                color: Colors.grey[300]!,
                width: 1.0,
              ),
            ),
          ),
          child: QuillToolbar.simple(
            configurations: QuillSimpleToolbarConfigurations(
              // fontFamilyValues: ,
              controller: _quillControllers[0],
              showSmallButton: true,
              showCodeBlock: false,
              showSearchButton: false,
              showQuote: true,
              showAlignmentButtons: true,
              showHeaderStyle: true,
              toolbarSectionSpacing: .5,
              toolbarSize: .5,
              sharedConfigurations: const QuillSharedConfigurations(
                locale: Locale('en'),
              ),
            ),
          ),
        ),
        Container(
          width: double.maxFinite,
          padding: EdgeInsets.all(4),
          color: Colors.amber,
          child: Text(
            sectionName,
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ],
    );
  }
}```

</details>


### Screenshots or Video
![editor_stuck_infinitely](https://github.com/singerdmx/flutter-quill/assets/19505269/a32b9427-d024-48bc-910f-e0e08e7de4da)


### Logs
Launching lib\main.dart on Edge in debug mode...
This app is linked to the debug service: ws://127.0.0.1:58130/U-cSlB8U0sA=/ws
Debug service listening on ws://127.0.0.1:58130/U-cSlB8U0sA=/ws
Connecting to VM Service at ws://127.0.0.1:58130/U-cSlB8U0sA=/ws
2 add more pressed
<details><summary>Logs</summary>

```console
[Paste your logs here]
@softwaredementor softwaredementor added the bug Something isn't working label Apr 27, 2024
@softwaredementor
Copy link
Author

softwaredementor commented Apr 27, 2024

editor_stuck_infinitely

Observe that in the second editor after the cursor nothing is typed or shown on screen. The whole application froze

@kairan77
Copy link

kairan77 commented May 5, 2024

It should not freeze.

Using _quillControllers to manage a 'list' of quill controllers is problematic, especially when you are only ever using the first element in the list. Calling widgets.add() and set state is pure evil, of course it won't work. In Flutter, manage values, don't manage widgets. Widgets should be managed by flutter's rendering engine 98% of the time, the other 2% are extremely hard to justify even at the expert level.

Encapsulate value of content of each section in something like a Section value object like Section{int index, String content, etc}, or just String if you only ever care about the content.

Then declares a PropertyValueNotifier<List

> sections in your top level widget state, to hold the list of section values.

Design a widget SectionEditor which takes in a section and internally wrap a quill editor inside.

Let SectionEditor manage its own scroll controller, quillController, and focusNode and configuration (ie. declare those objects in its State as private fields).

What you want to have is probably a ValueNotifier<QuillController?> which sends notification to your HeaderWidget to rebuild itself upon active section change. In the parent widget which holds the listViewBuilder which in turn holds the list of SectionEditors, have a ValueNotifier<QuillController?> activeController, Modify SectionEditor to also takes in a callback function which update the active quill controller to the controller of the active SectionEditor,

Pass the activeController to your HeaderWidget

lastly, you may want to pass a Key to the ToolBar derived from the content or index of your activeQuillController, if you are finding the state of the ToolBar is not updating when activeSection is changed.

Of course you should hook active section change call back with some sort of gesture detection or button click in your SectionEditor.

Irrelevant to the problem at hand but may be an improvement to your code anyway:

Ditch Provider if possible especially when the object you are passing is not that deep in the stack, why not pass ActiveSection directly from your calling widget to the current widget?

Favor ValueNotifier over setState

PropertyValueNotifier, is just ValueNotifier with its notifylistener method exposed, if you are not familiar with its purpose or usage just google it.

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