Home > Software design >  problem getting data from api as Future inside build() method in flutter
problem getting data from api as Future inside build() method in flutter

Time:11-14

My problem is with Futures, because they should be obtained before build() method executed, as the documentation states:

The future must be obtained earlier, because if the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.

I know that Futures should be called in initstate() function before the build method executed, but my case is different.

I want to get data from api as a Future, but the request I am sending to the api needs some parameters that user should select inside the screen's build() method.

And I don't know what the parameter of the request will be until user selects in build() method, and I have to call the api in the build() method and use FutureBuilder there, but that makes FutureBuilder to get constantly called, and I don't want that.

basically, I don't want to call FutureBuilder indefinetely, and I can't put my Future inside initState() because the Future needs some parameters that user later selects when the screen is shown inside build() method.

inside the build method:

FutureBuilder<List<LatLng>>(
                        builder: (context, snapshot) {
                          if (snapshot.hasData) {
                            return PolylineLayer(
                              polylines: [
                                Polyline(
                                    points: snapshot.data!,
                                    strokeWidth: 4,
                                    color: Colors.purple),
                              ],
                            );
                          } else if (snapshot.hasError) {
                            return Text("${snapshot.error}");
                          } else {
                            return Container();
                          }
                        },
                        future: Provider.of<NavigationProvider>(context)
                            .getNavigationPoints(pointToGoTo!),
                      ),

now if you look at the code, at the final lines, I am sending the parameter pointToGoTo to the function which calls the backend.

simply, I want to get rid of calling api and getting data back as a Future inside build method, I want to do it in initState or somewhere else that prevents the build methods calling backend indefinitely.

is there any way to fix this problem? Thanks in advance.

CodePudding user response:

Firstly, create future state variable and a nullable params and use it with conditional if while using FutureBuilder.

I will recommend checking Fixing a common FutureBuilder and StreamBuilder problem

Now you can follow this example. It is missing progressBar on API recall, StreamBuilder might be better option in cases like this.

class Foo extends StatefulWidget {
  const Foo({super.key});

  @override
  State<Foo> createState() => _FooState();
}

class _FooState extends State<Foo> {
  int? params;

  Future<int> fetch(int? data) async {
    await Future.delayed(Duration(seconds: 1));
    return (params ?? 0) * 2;
  }

  late Future<int> future = fetch(params);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          DropdownButton<int?>(
            value: params,
            items: List.generate(
                12,
                (index) => DropdownMenuItem(
                      value: index,
                      child: Text("$index"),
                    )).toList(),
            onChanged: (value) {
              future =
                  fetch(params); // this will only call api with update data
              setState(() {
                params = value;
              });
            },
          ),
          if (params != null)
            FutureBuilder<int>(
              future: future,
              builder: (context, snapshot) {
                if (snapshot.hasData) return Text("${snapshot.data}");
                return CircularProgressIndicator();
              },
            )
        ],
      ),
    );
  }
}

CodePudding user response:

class Testing extends StatefulWidget {
  const Testing({super.key});

  @override
  State<Testing> createState() => _TestingState();
}

class _TestingState extends State<Testing> {

bool isFetched = false;

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Consumer<SomethingProvider>(
      builder: (context, prov, child) {
        if (!isFetched) {
          prov.getData("a", "b");
          Future.delayed(const Duration(milliseconds: 200), () {
            isFetched = true;
          });
        }
        if (prov.newData.isNotEmpty) {
          return Column(
              // make widget tree from here
              );
        } else {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    ),
  );
 }
}

class SomethingProvider extends ChangeNotifier {
  List newData = [];

  Future getData(param1, param2) async {
    newData = ["testingdata"];
  }
}
  • Related