Reputation: 251
I have a TextFormField that reloads the current screen when I tap on it to enter text. When I tap on the formfield the software keyboard is displayed briefly before the entire screen reloads and renders all the widgets again. I am running the app on an Android device.
Container(
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Your input cannot be empty';
}
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
if (_formKey.currentState.validate()) {
print('validated');
}
},
child: Text('Save'),
),
),
],
),
),
margin: EdgeInsets.only(top:8.0),
),
Upvotes: 20
Views: 11584
Reputation: 303
This problem can be anything. Code structure could be one.
In my case I had a FutureBuilder with a auth provider in my root widget. So when the keyboard resizes the screen it would rebuild that specific FutureBuilder. Why? I don't know.
The solution for me was to move that specific that provider into initState. In the end the text controller focus was working correctly.
Lesson: check above state or builders or providers and refactor some code.
Upvotes: -1
Reputation: 21
Any chance your widget is somewhere inside a StreamBuilder? I was having the exact same problem and it turned out to be a StreamBuilder issue.
Say you have a function getStream()
which returns a string. For now we'll call your widget with the container MyWidget(). Here would be the faulty way of building this:
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: getStream(),
builder: (context, snapshot) {
//your logic here
return MyWidget();
},
);
}
The issue here is that every time something on the screen has to change, build is going to be called again. In the code above, calling build calls getStream() again, which creates a new stream instead of using the one that was already there.
When you tap on a text field and it's focused on, build is called. When build is called, a new stream is made. When this new stream is created, widgets returned by StreamBuilder
are also re-built (in this case, MyWidget()
). And when MyWidget()
is rebuilt, so is the text field inside of it, which starts out unfocused.
So? How do we fix this? First, you wanna make sure the widget returning StreamBuilder
is stateful (we're gonna refer to this one as RootWidget()
). Second, you want to declare a variable with your stream inside the state. It would look something like this:
class RootWidgetState extends State<RootWidget> {
final Future<E> _myStream= getStream();
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _myStream,
builder: (context, snapshot) {
//your logic here
return MyWidget();
},
);
}
}
Where E
just represents what kind of stream you're getting. If for some reason you can't access getStream() in the initializer, replace final
with late
, and initialize the variable in initState()
.
I also asked about this. Here's my question thread.
Upvotes: 0
Reputation: 6224
When TextFormField
focused the size of screen will changed because of the appearance of keyboard, that cause rebuild of state, you cant prevent re-build of state.
Instead of trying prevent re-build state, you need to solve problems which happen when state do re-build, one of common problem is declaration and initialization variables inside build(BuildContext context){ ... }'
function.
The main problem, when you need to get some data related of context
(like size of screen), in this case I prefer to pass this value from parent Widget
...
For example this code will cause problem when re-build state:
@override
Widget build(BuildContext context) {
double? _screenHeight = MediaQuery.of(context).size.height;
return Container();
}
To solve problem get _screenHeight
from parent, to know how to do that look at https://stackoverflow.com/a/50289032/2877427
Upvotes: 0
Reputation: 606
Check if you are using MediaQueries wrongly in your project, I had similar issue and it stopped when I changed the MediaQuery in my case:
Size _size = MediaQuery.of(context).size;
removing this piece of code fixed my app.
Upvotes: 0
Reputation: 70
Yes, that happens because when the keyboard appears, the flutter scaffold gets resize to the current available screen size. So, we can easily handle this by preventing the scaffold size change. I suggest to set scaffold resizeToAvoidBottomInset property false. If it's true the body and the scaffolds floating widgets should size themselves to avoid the onscreen keyboard whose height is defined by the ambient MediaQuery's, MediaQueryData,viewInsets bottom property.
Solution:
resizeToAvoidBottomInset: false,
Complete example:
@override
Widget build(BuildContext context) {
setDisplayData();
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: getAppBar(),
body: OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? _buildVerticalLayout()
: _buildHorizontalLayout();
},
),
);
Upvotes: 0
Reputation: 11
I had the same Problem. this was my code
class MainPage extends StatefulWidget {
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Model model = Model();
@override
Widget build(BuildContext context) {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
var mediaWidth = MediaQuery.of(context).size.width / 2.0;
return Scaffold(
...
and I solved this problem by declaring the _formKey outside of build method. and this worked for me.
class MainPage extends StatefulWidget {
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Model model = Model();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
var mediaWidth = MediaQuery.of(context).size.width / 2.0;
return Scaffold(
...
hope it will help you
Upvotes: 1
Reputation: 1233
The problem is that the controller of the TextFormField is rebuild when you click on the field, and that's the reason of your issue.
So to solve that, did you try to create a Statefull widget and then creating a TextEditingController in the State of this widget and passing it as an argument to the TextFormField ?
Upvotes: 2