Search code examples
flutterdartweb-applicationsflutter-web

Stop a TextField from resetting after/while dragging its parent


So I'm currently creating this little web app that's basically like https://classroomscreen.com/app. It is based on different types of Windows, which all have their features. (Text Window, Media Window, Checklist Window, etc.). Recently I finished the Text Window, which looks like this:

enter image description here

You can change both, the title and the caption. But when you drag the window to a new position, the title and caption reset.

To create windows, I created different widgets, which all extend the Window class. The Window class looks like this:

class Window extends StatefulWidget {
  SystemMouseCursor cursor = SystemMouseCursors.grab;
  String title = "";
  final Widget child;

  double x = Random().nextDouble() * 500;
  double y = Random().nextDouble() * 500;

  TextEditingController controller = TextEditingController();
  Window({
    Key? key,
    required this.title,
    required this.child,
  }) : super(key: UniqueKey());

  @override
  State<Window> createState() => _WindowState();
}

class _WindowState extends State<Window> {
  bool started = false;
  @override
  Widget build(BuildContext context) {
    if (!started) {
      setState(() {
        widget.controller.text = widget.title;
      });
    }

    return Positioned(
      left: widget.x,
      top: widget.y,
      child: GestureDetector(
        child: Draggable<Widget>(
          child: Container(
            width: 406.0,
            height: 406.0,
            decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(15.0),
                color: const Color.fromARGB(255, 249, 249, 249),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.05),
                    blurRadius: 12.0,
                    spreadRadius: 6.0,
                    offset: const Offset(4.0, 4.0),
                  ),
                  BoxShadow(
                    color: Colors.white70.withOpacity(0.05),
                    blurRadius: 12.0,
                    spreadRadius: 6.0,
                    offset: const Offset(-4, -4),
                  )
                ]),
            child: Stack(
              children: [
                const Positioned(
                  right: 0,
                  bottom: 0,
                  child: MouseRegion(
                    cursor: SystemMouseCursors.resizeDownRight,
                    child: SizedBox(
                      height: 15,
                      width: 15,
                    ),
                  ),
                ),
                Positioned(
                  top: 84,
                  left: 24,
                  right: 24,
                  bottom: 24,
                  child: Container(
                    child: widget.child,
                  ),
                ),
                Positioned(
                  top: 0,
                  right: 0,
                  left: 0,
                  child: Container(
                    width: 406.0,
                    height: 60.0,
                    decoration: BoxDecoration(
                      borderRadius: const BorderRadius.vertical(
                        top: Radius.circular(15.0),
                      ),
                      color: Colors.white,
                      boxShadow: [
                        BoxShadow(
                          color: Colors.black.withOpacity(0.05),
                          offset: const Offset(0, 6.0),
                          blurRadius: 12.0,
                        ),
                      ],
                    ),
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 24.0),
                      child: Align(
                        alignment: Alignment.centerLeft,
                        child: SizedBox(
                          width: 358.0,
                          child: TextField(
                            decoration: const InputDecoration(
                              border: InputBorder.none,
                              hintText: "",
                            ),
                            controller: widget.controller,
                            style: GoogleFonts.montserrat(
                              fontSize: 20.0,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
          feedback: Material(
            type: MaterialType.transparency,
            child: Container(
              width: 406.0,
              height: 406.0,
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(15.0),
                  color: const Color.fromARGB(255, 249, 249, 249),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.015),
                      blurRadius: 12.0,
                      spreadRadius: 6.0,
                      offset: const Offset(0.0, 0.0),
                    ),
                  ]),
              child: Stack(
                children: [
                  Positioned(
                    top: 84,
                    left: 24,
                    right: 24,
                    bottom: 24,
                    child: Container(
                      child: widget.child,
                    ),
                  ),
                  Positioned(
                    top: 0,
                    right: 0,
                    left: 0,
                    child: Container(
                      width: 406.0,
                      height: 60.0,
                      decoration: BoxDecoration(
                          borderRadius: const BorderRadius.vertical(
                            top: Radius.circular(15.0),
                          ),
                          color: Colors.white,
                          boxShadow: [
                            BoxShadow(
                              color: Colors.black.withOpacity(0.05),
                              offset: const Offset(0, 6.0),
                              blurRadius: 12.0,
                            )
                          ]),
                      child: Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 24.0),
                        child: Align(
                          alignment: Alignment.centerLeft,
                          child: SizedBox(
                            width: 358.0,
                            child: TextField(
                              decoration: const InputDecoration(
                                border: InputBorder.none,
                                hintText: "",
                              ),
                              controller: widget.controller,
                              style: GoogleFonts.montserrat(
                                fontSize: 20.0,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  )
                ],
              ),
            ),
          ),
          childWhenDragging: Container(
            height: 500,
            width: 500,
            color: Colors.transparent,
          ),
          onDragEnd: (details) {
            //set the item to position 0 in the list
            setState(() {
              widget.x = details.offset.dx;
              widget.y = details.offset.dy;
            });
          },
        ),
      ),
    );
  }
}

As you can see, a child needs to be passed which is gonna be displayed in the main container. I am not sure if I am doing the extending thing right (I'm quite a beginner) but here's the code for my TextWindow:

class TextWindow extends Window {
  TextWindow({Key? key})
      : super(
          key: UniqueKey(),
          title: "Text",
          child: TextField(
            onChanged: (value) {},
            keyboardType: TextInputType.multiline,
            maxLines: 50,
            decoration: InputDecoration(
              hintText: getRandomHint(),
              border: InputBorder.none,
              hintStyle: GoogleFonts.montserrat(
                fontSize: 15.0,
                fontWeight: FontWeight.w500,
              ),
            ),
            style: GoogleFonts.montserrat(
              fontSize: 15.0,
              fontWeight: FontWeight.w600,
            ),
          ),
        );

  Widget build(BuildContext context) {
    return this;
  }
}

I can't find the point where a state update might reset the child to its origin.

To have a look at the issue yourself: https://weazleboii.github.io/webboard/index.html#/


Solution

  • I'm going to guess that you're seeing this because you don't actually put any state into your stateful widget. Whenever you drag the window, I guess it forces a repaint of all the widgets, and if your stateful widget kept the state of the text field, I imagine all would be well.

    So for example, in _WindowState you could have, in addition to what you have:

    class _WindowState extends State<Window> {
      String fieldValue = "";
    
      @override
      void initState() {
        super.initState();
        fieldValue = widget.title;
      }
    

    Now at this point, your stateful widget contains the fieldValue, so now when you change it, update the state, and use it. For example:

      ...TextField(
         ...,
         onChanged: (value) { 
            setState(() {
                fieldValue = value;
            });
         }
     )
    

    Now if you used fieldValue all should be okay.... I'd also move things like controller into the _WindowState class. (This won't fix the textfield that you pass in as a child (I wonder why you do that), as it doesn't have any state saved anywhere either.)