'Why is FutureBuilder called multiple times?

Excuse the ignorance i am new to Flutter.

I am practicing on a course I did and I have the following scenario:

I have a view where I consume a web service in the method getDataArray (FutureBuilder), and I just want it to be done only once when this view is accessed.

I also have a TextField, FutureBuilder and the TextField is in thebuild(). My widget is a StatefulWidget because it needs to be redrawn to show the character counter of my TextField, so I think this is the reason that every time I type something the build runs and call the FutureBuilder again.

How can I solve that?

    class Pagina3 extends StatefulWidget {
    @override
    Pagina3State createState() {
        return Pagina3State();
    }
    }

    class Pagina3State extends State<Pagina3> {
    String _input = "";

    //returns a web service
    Future getDataArray() async {
        final dynamic resp =
            await web.http_Web(webservices.url["DATOS"], "POST", {}, context);
        return resp;
    }

    Pagina3() {}

    @override
    Widget build(BuildContext context) {
        var validators = new Validators();
        var utils = new Utils();
        return Scaffold(
        appBar: AppBar(
            title: Text('Page 3'),
        ),
        body: SafeArea(
            child: Center(
            child: Form(
                child: Padding(
                padding: EdgeInsets.symmetric(horizontal: 10),
                child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                    TextFormField(
                        decoration: InputDecoration(
                        counter: Text('Letras ${_input.length.toString()}'),
                        icon: Icon(Icons.alternate_email),
                        hintText: '[email protected]',
                        labelText: 'Correo electrónico',
                        ),
                        validator: (value) => validators
                            .fn_checkValidation([validators.required(value)]),
                        onChanged: (valor) {
                        setState(() {
                            _input = valor;
                        });
                        },
                    ),
                    Container(
                        child: FutureBuilder(
                            future: getDataArray(),
                            builder: (context, AsyncSnapshot<dynamic> snapshot) {
                            if (snapshot.hasData) {
                                if (snapshot.data["ok"] == false) {
                                return Text("ha ocurrido un problema");
                                }
                                return (Text("Exito"));
                            } else {
                                return Center(child: CircularProgressIndicator());
                            }
                            }),
                    )
                    ],
                ),
                ),
            ),
            ),
        ),
        );
    }
    }


Solution 1:[1]

Anything that's within a widget's build() method can trigger anytime — this is, you shouldn't rely on anything critical that could have impact on it. For example, opening a keyboard or closing, is enough to trigger multiple calls to build() on a widget.

Your FutureBuilder is just a widget on your tree, and because of that, anytime it rebuilds (for example, whenever you type on the TextField), it will call your getDataArray() because it doesn't actually really knows if that has already been called or not.

If you just want it to be called once, you can assign it to a state member and assign it to your FutureBuilder. That will be preserved as long as that state exists on the widget tree.

You can for example assign it on initState() if you want to call it only per state lifecycle or at didChangeDependencies() for example, if you want to refresh that call anytime a dependency changes.

 class Pagina3State extends State<Pagina3> {
  late final Future myFuture;

  @override
  void initState() {
     myFuture = getDataArray();
  }

  @override
   Widget build(BuildContext context) {
       
       child: FutureBuilder(
              future: myFuture,
      )
   }
}

Solution 2:[2]

As suggested by Miguel, you should assign your future to a variable outside of build() scope so it is only run once.

Note that since Dart 2.0 you can use lazy initialization with late final variables to write this in a even shorter way:

 class Pagina3State extends State<Pagina3> {
   late final Future myFuture = getDataArray();

   @override
   Widget build(BuildContext context) {
       child: FutureBuilder(
              future: myFuture,
      )
   }
}

Solution 3:[3]

You can create a variable which return futureBuilder, so when you call setState only widget go rebuild but variable don't, so you can solve your issue.

 Widget futureWidget;

  @override
  void initState() {
    futureWidget = FutureBuilder(
      future: callme(),
      builder: (_, sanpshot) {
        print("future method call");
        if (sanpshot.hasData) {
          return Text(sanpshot.data.toString());
        }
        return CircularProgressIndicator();
      },
    );
    super.initState();
  }

  callme() async {
    await Future.delayed(Duration(seconds: 1));
    return "success";
  }

  @override
  Widget build(BuildContext context) {
    print("build method call");
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          RaisedButton(
            child: Text("press"),
            onPressed: () {
              setState(() {});
            },
          ),
          futureWidget,
        ],
      ),
    );
  }

Solution 4:[4]

That's because you are using setstate in your textfield action and it rebuild everything. I faced with similar problem with one of my apps and used a bool to avoid it, I named it firstRun, if it is true I called my future function in future builder if it's not I made it null like below ode sample. But I am not sure that is best way to do it, also you need to make this bool false in somewhere (in initstate perhaps);

bool firstRun = true;

 @override
    Widget build(BuildContext context) {...
                            ...
                             onChanged: (valor) {
                        firstRun = false;
                        setState(() {
                            _input = valor;
                        });
                        },
                      .....
                     FutureBuilder(
                            future: firtRun ? getDataArray() : null,
                            builder: (...

Solution 5:[5]

The solution to the problem is to add a delay to the future builder event. Connection occurs during standby and initstate is not triggered

 _initializeFirebase() async {
    //ADD DURATION
   await Future.delayed(Duration(seconds: 1));
   return Firebase.initializeApp();
 }

Run Future Builder

return FutureBuilder(
  // Initialize FlutterFire
  future: _initializeFirebase(),
  builder: (context, snapshot) {
    // Check for errors
    if (snapshot.hasError) {
      return Center(
        child: CircularProgressIndicator(),
      );
    }
    // Once complete, show your application
    if (snapshot.connectionState == ConnectionState.done) {
      return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Chat Application',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          initialRoute: SignScreen.routeName,
          routes: {
            SignScreen.routeName: (content) => SignScreen(),
            ChatScreen.routeName: (content) => ChatScreen(),
            LoginScreen.routeName: (content) => LoginScreen(),
            RegisterScreen.routeName: (content) => RegisterScreen(),
          });
    }

    // Otherwise, show something whilst waiting for initialization to complete
    return Center(
      child: CircularProgressIndicator(),
    );
  },
);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Pom12
Solution 3 Viren V Varasadiya
Solution 4
Solution 5 Mehmet Emin Sayım