14

I'm developing a Flutter app which prompts a form asking for some personal info.

The problem is that the page is being rebuilt every time something happens, like when the screen orientation changes or when a text field gets the focus (the keyboard appears and disappears right away, preventing the user from typing anything).

Obviously something is triggering unwanted rebuilds, but I couldn't find out what and where.

When I plug this page as the homepage, everything works fine. The issue happens when I plug the page at its intended position, after an animation is displayed on a splash screen, so I guess it has something to do with my problem.

Main class :

import 'package:flutter/material.dart';
import './view/SplashScreen.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SplashScreen(),
    );
  }
}

Splash screen :

import 'package:flutter/material.dart';
import 'dart:async';
import './UserLoader.dart';

class SplashScreen extends StatefulWidget {
  @override
  _SplashScreenState createState() => new _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen>
    with SingleTickerProviderStateMixin {
  AnimationController _iconAnimationController;
  CurvedAnimation _iconAnimation;

  @override
  void initState() {
    super.initState();

    _iconAnimationController = new AnimationController(
        vsync: this, duration: new Duration(milliseconds: 2000));

    _iconAnimation = new CurvedAnimation(
        parent: _iconAnimationController, curve: Curves.easeIn);
    _iconAnimation.addListener(() => this.setState(() {}));

    _iconAnimationController.forward();

    startTimeout();
  }

  @override
  Widget build(BuildContext context) {
    return new Material(
      color: Colors.white,
      child: new InkWell(
        child: new Center(
          child: new Container(
            width: 275.0,
            height: 275.0,
            decoration: new BoxDecoration(
              image: new DecorationImage(
                  colorFilter: new ColorFilter.mode(
                      Colors.white.withOpacity(_iconAnimation.value),
                      BlendMode.dstATop),
                  image: new AssetImage("images/logo.png")),
            ),
          ),
        ),
      ),
    );
  }

  void handleTimeout() {
    Navigator.of(context).pushReplacement(new MaterialPageRoute(
        builder: (BuildContext context) => new UserLoader()));
  }

  startTimeout() async {
    var duration = const Duration(seconds: 3);
    return new Timer(duration, handleTimeout);
  }
}

Faulty page :

import 'package:flutter/material.dart';

class UserLoader extends StatefulWidget {
  @override
  _UserLoaderState createState() => new _UserLoaderState();
}

class _UserLoaderState extends State<UserLoader> {
  @override
  Widget build(BuildContext context) {
    final _formKey = new GlobalKey<FormState>();
    final _emailController = new TextEditingController();

    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Informations"),
          actions: <Widget>[
            new IconButton(
                icon: const Icon(Icons.save),
                onPressed: () {
                  // unrelated stuff happens here
                })
          ],
        ),
        body: new Center(
          child: new SingleChildScrollView(
              child: new Form(
                  key: _formKey,
                  child: new Column(children: <Widget>[
                    new ListTile(
                      leading: const Icon(Icons.email),
                      title: new TextFormField(
                        decoration: new InputDecoration(
                          hintText: "Email",
                        ),
                        keyboardType: TextInputType.emailAddress,
                        controller: _emailController,
                        validator: _validateEmail,
                      ),
                    ),
                  ]))),
        ));
    }}

Can anybody help me find out why the page is continuously rebuilding itself ?

7
  • 2
    "like when the screen orientation changes or when a text field gets the focus", I'd say that is expected. When the size or format of the screen changes the UI needs to be rebuilt according to the new constraints. Commented May 11, 2018 at 13:18
  • @GünterZöchbauer Well I can understand that. But what should I do to let users write in the text fields, instead of having the keyboard disappear each time they try to write ? Feel free to edit the question in better terms, if you think the current writing is misleading.
    – Daneel
    Commented May 11, 2018 at 13:23
  • If the keyboard disappears when you write this is caused by something else. In my comment (and your quote) only "location changes" and "when a text field gets the focus" are mentioned. Commented May 11, 2018 at 13:36
  • 2
    I guess the issue is caused by the TextFormField being placed inside a scrollable area. I think this is known issue. I don't know a workaround. Search the Flutter issues in GitHub for similar issues. Commented May 11, 2018 at 13:38
  • 1
    Sounds like github.com/flutter/flutter/issues/10826 Commented May 11, 2018 at 16:23

2 Answers 2

13

I solved the problem by simply changing the class like this :

import 'package:flutter/material.dart';

class UserLoader extends StatefulWidget {
  @override
  _UserLoaderState createState() => new _UserLoaderState();
}

class _UserLoaderState extends State<UserLoader> {
  Widget _form; // Save the form

  @override
  Widget build(BuildContext context) {
    if (_form == null) { // Create the form if it does not exist
      _form = _createForm(context); // Build the form
    }
    return _form; // Show the form in the application
  }

  Widget _createForm(BuildContext context) {
    // This is the exact content of the build method in the question

    final _formKey = new GlobalKey<FormState>();
    final _emailController = new TextEditingController();

    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Informations"),
          actions: <Widget>[
            new IconButton(
                icon: const Icon(Icons.save),
                onPressed: () {
                  // unrelated stuff happens here
                })
          ],
        ),
        body: new Center(
          child: new SingleChildScrollView(
              child: new Form(
                  key: _formKey,
                  child: new Column(children: <Widget>[
                    new ListTile(
                      leading: const Icon(Icons.email),
                      title: new TextFormField(
                        decoration: new InputDecoration(
                          hintText: "Email",
                        ),
                        keyboardType: TextInputType.emailAddress,
                        controller: _emailController,
                        validator: _validateEmail,
                      ),
                    ),
                  ]))),
        ));
    }
  }
}

Hope this may help someone else someday.

9
  • 10
    You should probably note the changes you did to the class so other people won't have to look trough the code to find an answer.
    – Nato Boram
    Commented Feb 13, 2019 at 18:57
  • 1
    @NatoBoram You're right, I added some comments in the code to highlight the changes :)
    – Daneel
    Commented Feb 21, 2019 at 9:54
  • 1
    @Sibin Sorry, this project is a bit old for me, but as I recall, my _validateEmail returned null if the email was correct, or a message error (a simple string) which was displayed automatically underneath the field. The Save button had a method calling something like _formKey.currentState.validate() to check if everything was alright - if the email was wrong, the validator returned false and prevented the "save" action. Flutter handled it all nicely. Hope this helps, otherwise you can post a question on SO to get better answers.
    – Daneel
    Commented Feb 21, 2019 at 17:15
  • 1
    @Daneel Thanks for the reply. In my project implemented streambuilder then everything is working perfectly now .
    – Sibin
    Commented May 23, 2019 at 9:00
  • 1
    Dear, You save my life and save my time. Please take my heartiest thanks
    – putulputul
    Commented Jun 18, 2019 at 9:37
5

All you need to do is move this row

final _formKey = new GlobalKey<FormState>();

from build method to state class declaration (i.e. outside from build). Key must be created once when class is created. In your case the key is re-created each build action.

Not the answer you're looking for? Browse other questions tagged or ask your own question.