Search code examples
flutterflutter-layoutflutter-dependencies

Flutter-Quill editor not working inside a scrollable form


I would like to put my QuillEditor inside my form and not on a dedicated page of my app.

I've tried with a Column inside a SingleChildScrollView and with a ListView widget, but the result is the same:

  • since there is no text, the editor is small and I would like it to be "expanded" (I know it can't be as such inside a scroll, but you get the point);
  • the overflow of the text does not make the page scroll accordingly, so the users can't see what they are typing.

Here's some code:

return Scaffold(
  appBar: AppBar(
    title: Text("Reply form"),
    actions: [
      IconButton(
        onPressed: () {},
        icon: Icon(Icons.send),
        tooltip: "Send",
      )
    ],
  ),
  body: SafeArea(
    child: Form(
      child: ListView(
        children: [
          Text("From: [email protected]"),
          SizedBox(height: 8),
          chipInputField(
            label: "To",
            onChanged: (List<Object?> o) {},
            initialValue: ["[email protected]"],
          ),
          chipInputField(
            label: "Cc",
            onChanged: (List<Object?> o) {},
            initialValue: ["[email protected]"],
          ),
          chipInputField(
            label: "Bcc",
            onChanged: (List<Object?> o) {},
            initialValue: ["[email protected]"],
          ),
          QuillEditor(
            controller: quillController,
            scrollable: true,
            scrollController: ScrollController(),
            focusNode: FocusNode(),
            padding: EdgeInsets.all(5),
            autoFocus: true,
            readOnly: false,
            expands: false,
            placeholder: "compose_email",
          ),
          QuillToolbar.basic(
            controller: quillController,
            showUnderLineButton: false,
            showStrikeThrough: false,
            showColorButton: false,
            showBackgroundColorButton: false,
            showListCheck: false,
            showIndent: false,
          ),
        ],
      ),
    ),
  ),
);

First issue: the compose email part is not expanded inside the application page

the compose email part is not expanded inside the application page

Second issue: as long as I write some text, the widget is not scrolling

the keyboard is over the text and the widget is not scrolling

Edit: I'm using Flutter 2.2.3 and flutter_quill: 1.3.3


Solution

  • So after many tries to solve this answer I've realized the following and came up with a solution that might help you.

    a textField in a listview that has maxlines: null will auto-scroll the listView with no issues but for some reason, flutter_quill has some issues

    now my work around this is as follows ( full code will be at the bottom ):

    1. first we define a couple of variables in the state class of the widget that has this form
    final quill.QuillController quillController = quill.QuillController.basic();
    final FocusNode editorFocusNode = FocusNode();
    bool isToolBarVisible = true;
    

    you already have the controller defined but for the other 2 attributes, you will need them to show/hide the toolbar based on the focused node.

    1. instead of using a simple ListView, we use a customScrollView which makes it possible for us to create a more complex listview, it uses slivers ( a sliver is a scrollable widget, you can learn more about it )
    Form(
              child: CustomScrollView(
                slivers: [
                  SliverAppBar(
                    pinned: true,
                    toolbarHeight: isToolBarVisible ? 50 : 0,
                    title:
                        Visibility(visible: isToolBarVisible, child: getToolBar()),
                  ),
                  SliverToBoxAdapter(
                    child: Column(
                      children: getTextFields(),
                    ),
                  ),
                  SliverFillRemaining(
                    hasScrollBody: true,
                    child: getEditor(),
                  ),
                ],
              ),
    

    using the sliver app bar we can pin the quill toolbar, using the sliver to box adapter we can add the normal text fields, and then to expand the quill editor we use sliverFillRemaining.

    here are the sub-functions:

     Widget getToolBar() {
        return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: quill.QuillToolbar.basic(
            controller: quillController,
            showUnderLineButton: false,
            showStrikeThrough: false,
            showColorButton: false,
            showBackgroundColorButton: false,
            showListCheck: false,
            showIndent: false,
          ),
        );
      }
    
      List<Widget> getTextFields() {
        return [
          Text("From: [email protected]"),
          SizedBox(height: 8),
          TextField(
            decoration: InputDecoration(labelText: "To"),
          ),
          TextField(
            decoration: InputDecoration(labelText: "Cc"),
          ),
          TextField(
            decoration: InputDecoration(labelText: "Bcc"),
          ),
        ];
      }
    
      Widget getEditor() {
        return QuillEditor(
          controller: quillController,
          scrollable: true,
          scrollController: ScrollController(),
          focusNode: editorFocusNode,
          padding: EdgeInsets.all(5),
          autoFocus: true,
          readOnly: false,
          expands: false,
          placeholder: "compose_email",
        );
      }
    

    replace the normal textFields with your chipInputField

    finally here is the full code:

    import 'package:flutter/material.dart';
    import 'package:flutter_quill/flutter_quill.dart' as quill;
    import 'package:flutter_quill/widgets/editor.dart';
    
    class FlutterQuillForm extends StatefulWidget {
      @override
      _FlutterQuillFormState createState() => _FlutterQuillFormState();
    }
    
    class _FlutterQuillFormState extends State<FlutterQuillForm> {
      final quill.QuillController quillController = quill.QuillController.basic();
      final FocusNode editorFocusNode = FocusNode();
      bool isToolBarVisible = true;
      @override
      void initState() {
        editorFocusNode.addListener(() {
          setState(() {
            isToolBarVisible = editorFocusNode.hasFocus;
          });
        });
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Reply form"),
            actions: [
              IconButton(
                onPressed: () {},
                icon: Icon(Icons.send),
                tooltip: "Send",
              )
            ],
          ),
          body: SafeArea(
            child: Form(
              child: CustomScrollView(
                slivers: [
                  SliverAppBar(
                    pinned: true,
                    toolbarHeight: isToolBarVisible ? 50 : 0,
                    title:
                        Visibility(visible: isToolBarVisible, child: getToolBar()),
                  ),
                  SliverToBoxAdapter(
                    child: Column(
                      children: getTextFields(),
                    ),
                  ),
                  SliverFillRemaining(
                    hasScrollBody: true,
                    child: getEditor(),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    
      Widget getToolBar() {
        return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: quill.QuillToolbar.basic(
            controller: quillController,
            showUnderLineButton: false,
            showStrikeThrough: false,
            showColorButton: false,
            showBackgroundColorButton: false,
            showListCheck: false,
            showIndent: false,
          ),
        );
      }
    
      List<Widget> getTextFields() {
        return [
          Text("From: [email protected]"),
          SizedBox(height: 8),
          TextField(
            decoration: InputDecoration(labelText: "To"),
          ),
          TextField(
            decoration: InputDecoration(labelText: "Cc"),
          ),
          TextField(
            decoration: InputDecoration(labelText: "Bcc"),
          ),
        ];
      }
    
      Widget getEditor() {
        return QuillEditor(
          controller: quillController,
          scrollable: true,
          scrollController: ScrollController(),
          focusNode: editorFocusNode,
          padding: EdgeInsets.all(5),
          autoFocus: true,
          readOnly: false,
          expands: false,
          placeholder: "compose_email",
        );
      }
    
      @override
      void dispose() {
        super.dispose();
        quillController.dispose();
      }
    }
    
    

    in the focus node listener, we can set the state of the toolbar to be visible or invisible and don't forget to dispose the controllers and the focus node.

    screenshots:

    toolbar hidden toolbar showing auto-scroll