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 :
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
- The first time you focus your
TextField
your color is not null, the widget tree does not change and everything works. - When you unfocus, the widget tree is modified so your
TextField
state is lost => The text is "reset" - When you refocus, the widget tree changes again, so your
TextField
changes once more, causing the keyboard to be close - 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 !