Home > Blockchain >  Reload color container to null erase TextField inside Flutter
Reload color container to null erase TextField inside Flutter

Time:09-25

I have a Container wrapping a CupertinoTextField within Stack.

When I finish to write on textfield I press a button and clear the focus with FocusNode, then reload UI.

I want to reload the color container to null for a use case but when I'm reloading, the textfield seems 'erased'. Then When I'm clicking again on textfield something is blocking the textfield . Only on the second click I'm able to retrieve textfield focus.

Here is the preview of what I describe above :

enter image description here

Here is the full code for this :

class EditorTextPage extends StatefulWidget {
  const EditorTextPage({Key? key}) : super(key: key);

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

class _EditorTextPageState extends State<EditorTextPage> {
  FocusNode focus = FocusNode();
  bool displayEditor = true;

  @override
  void initState() {
    focus.addListener(() {
      if (focus.hasFocus) {
        setState(() {
          displayEditor = true;
        });
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: displayEditor ? Colors.red : null,
      child: SafeArea(
        bottom: false,
        child: Stack(
          children: [
            Positioned(
              top: 100,
              left: 200,
              child: IntrinsicWidth(
                child: CupertinoTextField(
                  focusNode: focus,
                  minLines: 1,
                  maxLines: 3,
                  padding: EdgeInsets.all(10),
                  cursorColor: Colors.white,
                  decoration: BoxDecoration(color: Colors.lightBlueAccent),
                ),
              ),
            ),
            Align(
                alignment: Alignment.bottomCenter,
                child: Container(
                  margin: EdgeInsets.only(bottom: 50),
                  child: CupertinoButton(
                    color: Colors.black,
                    onPressed: () {
                      setState(() {
                        if (focus.hasFocus) {
                          focus.unfocus();
                          displayEditor = false;
                        }
                      });
                    },
                    child: Text(
                      "DONE",
                    ),
                  ),
                )),
          ],
        ),
      ),
    );
  }
}

Why is this happening ? And how to fix it ?

CodePudding user response:

What is the issue

Container uses a ColoredBox to display the color. However ColoredBox must have a color, so in reality Container uses ColoredBox only if color!=null.

Which gives you the following widget tree:

  • With color!=null: [Container -> ColoredBox -> Stack]
  • With color==null: [Container -> Stack]

Now if you know how flutter state works, you will know that a state re-attached to the same widget if the widget tree at the same level does not change. Here, since the widget tree above CupertinoTextField changes (ColoredBox is replaced with a Stack or vise versa), the state in not properly rebuild when the color becomes null or stops being null.

Here is what happens in details

  1. The first time you focus your TextField your color is not null, the widget tree does not change and everything works.
  2. When you unfocus, the widget tree is modified so your TextField state is lost => The text is "reset"
  3. When you refocus, the widget tree changes again, so your TextField changes once more, causing the keyboard to be close
  4. You are back at 1

For more on Flutter keys see: When to Use Keys

How to solve it

The key (:o) is to make flutter understand that the Stack which is bellow the Container and then bellow the ColoredBox are the same. To do so, assign it a GlobalKey (see the video I gave you if you don't understand why).

Here is the implementation:

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

void main() {
  runApp(MaterialApp(home: EditorTextPage()));
}

class EditorTextPage extends StatefulWidget {
  const EditorTextPage({Key? key}) : super(key: key);

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

class _EditorTextPageState extends State<EditorTextPage> {
  final focus = FocusNode();
  bool displayEditor = true;

  final _key = GlobalKey();

  @override
  void initState() {
    focus.addListener(() {
      if (focus.hasFocus) {
        setState(() {
          focus.requestFocus();
          displayEditor = true;
        });
      }
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        color: displayEditor ? Colors.red : null,
        child: SafeArea(
          bottom: false,
          child: Stack(
            key: _key,
            children: [
              Positioned(
                top: 100,
                left: 200,
                child: IntrinsicWidth(
                  child: CupertinoTextField(
                    focusNode: focus,
                    minLines: 1,
                    maxLines: 3,
                    padding: EdgeInsets.all(10),
                    cursorColor: Colors.white,
                    decoration: BoxDecoration(color: Colors.lightBlueAccent),
                  ),
                ),
              ),
              Align(
                alignment: Alignment.bottomCenter,
                child: Container(
                  margin: EdgeInsets.only(bottom: 50),
                  child: CupertinoButton(
                    color: Colors.black,
                    onPressed: () {
                      if (focus.hasFocus) {
                        setState(() {
                          focus.unfocus();
                          displayEditor = false;
                        });
                      }
                    },
                    child: Text(
                      "DONE",
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Thanks for this challenge this was truly fun!

Want another fix without keys?

The other option is to avoid modifying the widget tree altogether. The solution is therefore for the color never to be null, set it to transparent for example:

// GOOD
color: displayEditor ? Colors.red : Colors.transparent

// BAD
color: displayEditor ? Colors.red : null

What to use ?

I don't know.

In the first option it challenges flutter tree algorithm

In the second option, it's whether rendering a transparent color is challenging for flutter, which I don't know and can't find.

In someone has an opinion this would be very interesting !

  • Related