4

How can I fetch data only once while using FutureBuilder to show a loading indicator while fetching?

The problem is that every time the user opens the screen it will re-fetch the data even if I set the future in initState().

I want to fetch the data only the first time the user opens the screen then I will use the saved fetched data.

should I just use a stateful widget with a loading variable and set it in setState()?

I'm using Provider package

Future<void> fetchData() async {
try {
  final response =
      await http.get(url, headers: {'Authorization': 'Bearer $_token'});......

and my screen widget:

    class _MyScreenState extends State<MyScreen> {
  Future<void> fetchData;

  @override
  void initState() {

    fetchData =
        Provider.of<Data>(context, listen: false).fetchData();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: fetchData,
      builder: (ctx, snapshot) =>
          snapshot.connectionState == ConnectionState.done
              ? Consumer<Data>(
                  builder: (context, data, child) => Text(data.fetchedData)): Center(
                               child: CircularProgressIndicator(),
                               ),
                        
    );
  }
}
AhWagih
  • 485
  • 5
  • 14

4 Answers4

3

A simple approach is by introducing a StatefulWidget where we stash our Future in a variable. Now every rebuild will make reference to the same Future instance:

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  Future<String> _future;

  @override
  void initState() {
    _future = callAsyncFetch();
    super.initState();
  }

  @override
  Widget build(context) {
    return FutureBuilder<String>(
      future: _future,
      builder: (context, snapshot) {
        // ...
      }
    );
  }
}

Or you can simply use a FutureProvider instead of the StatefulWidget above:

class MyWidget extends StatelessWidget {
  // Future<String> callAsyncFetch() => Future.delayed(Duration(seconds: 2), () => "hi");
  @override
  Widget build(BuildContext context) {
    // print('building widget');
    return FutureProvider<String>(
      create: (_) {
        // print('calling future');
        return callAsyncFetch();
      },
      child: Consumer<String>(
        builder: (_, value, __) => Text(value ?? 'Loading...'),
      ),
    );
  }
}
chiquitta
  • 43
  • 4
  • but this doesn't solve the problem and every time the page reopens the data will be fetched again. – AhWagih Aug 19 '20 at 02:44
  • Sure, but that's completely normal...if you need to keep the data stored then you should save the state in some manner, which can be anything (even a local database). It depends a lot on which kind of data is, and when you need to refresh it. – funder7 Jan 06 '21 at 23:23
3

If you want to fetch the data only once even if the widget rebuilds, you would have to make a model for that. Here is how you can make one:

class MyModel{
 String value;
 Future<String> fetchData() async {
 if(value==null){
 try {
  final response =
      await http.get(url, headers: {'Authorization': 'Bearer $_token'});......
 value=(YourReturnedString)
  }
 }
  return value;
 }
}

Don't forget to place MyModel as a Provider. In your FutureBuilder:

@override
  Widget build(context) {
    final myModel=Provider.of<MyModel>(context)
    return FutureBuilder<String>(
      future: myModel.fetchData(),
      builder: (context, snapshot) {
        // ...
      }
    );
  }
CoderUni
  • 5,474
  • 7
  • 26
  • 58
  • the thing is that I'm trying to figure out what is the best way to fetch the data form an api, sometimes I notice some apps that fetch the data every time the screen loads and some other apps just fetch the data when the screen first loads then when you revisit the same screen you find the data ready without loading so I want to achieve this. – AhWagih Aug 19 '20 at 13:05
  • 1
    it really depends. If your data changes frequently, then fetch it many times if you want. If it doesn't only fetch it once. It really all boils down in to how many times You really want to fetch it. – CoderUni Aug 19 '20 at 13:37
  • I did just read about "factory", which is used in place of constructor...the official example seems to be right for this use case, in fact is about caching an object. If you want to give it a read: https://dart.dev/guides/language/language-tour#factory-constructors – funder7 Jan 06 '21 at 23:19
1

You can implement provider and pass data among its child. Refer this example for fetching the data once and using it throughout its child.

0

As Aashutosh Poudel suggested, you could use an external object to maintain your state,

FOR OTHERS COMING HERE!

To manage state for large applications, the stateful widgets management becomes a bit painful. Hence you have to use an external state object that is shall be your single source of truth.

State management in flutter is done by the following libraries | services:

i. Provider: Well, i have personally played with this a little bit, even did something with it. I could suggest this for beginners.

ii. GetX: That one library that can do everything, its a good one and is recommended for novice || noob.

iii. Redux: For anyone coming from the react and angular world to flutter, this is a very handy library. I personally love this library, plus when you give it additional plugins, you are just superman

iv. Bloc: Best for data that is in streams. in other words, best for reactive programming approach....

Anyways, that was a lot given your question. Hope i helped

emanuel sanga
  • 815
  • 13
  • 17