Amit Maraj
Amit Maraj

Reputation: 251

Flutter TextFormField reloads current screen when focused

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

Answers (7)

Marshall Fungai
Marshall Fungai

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

saadman
saadman

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

AnasSafi
AnasSafi

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

Tarish
Tarish

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

Nuwantha Fernando
Nuwantha Fernando

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

karim hessam
karim hessam

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

Gaspard Merten
Gaspard Merten

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

Related Questions