简体   繁体   中英

Best practice of async call on flutter?

I don't want the async call fire every time when the widget rebuild. So, I call the async function in initState.

  1. call async in initState

    @override void initState() { someAsyncCall().then((result) { setState() { _result = result; } }); } @override Widget build(BuildContext context) { if (_result == null) { reutrn new Container(); } return new SomeWidget(); }
  2. use FutureBuilder

     @override void initState() { _future = someAsyncCall(); } @override Widget build(BuildContext context) { return new FutureBuilder( future: _future, builder: // do something ); }

Is there any side effect or bad practice on both solution?


I modify the demo app from flutter to explain why I call the async in initSate.
1. open app, print main
2. push screen to test, print test, main
3. pop test, print main

If I call async function in build, it calls three times.
What I want is, I need the async function call once, unless the main dispose / pop.
According to the answer from Rémi Rousselet, call async in initState is kind of a problem or wrong.
So, how can I make sure the async function call once?

   @override
   Widget build(BuildContext context) {
    print("build main");
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new RaisedButton(onPressed: () {
              Navigator.push(context, new MaterialPageRoute(builder: (context) {
                return new Test();
              }));
            }),
          ],
        ),
      )
    );
  }

 class Test extends StatelessWidget {

   @override
   Widget build(BuildContext context) {
     print("build Test");
     return new Scaffold(
       body:
       new FlatButton(onPressed: () {
         Navigator.pop(context);
       }, child: new Text("back"))
     );
   }

 }

Do I need to do something like

@override
Widget build(BuildContext context) {
  if (!mounted) {
    print("build main");
    // aysnc function call here
  }
}

While slightly more verbose, the FutureBuilder definitely is the way to go.

Calling async function within the initState and then doing a setState when they finish has a few problems. Most of the time while doing this, you won't bother about error catching or handling the loading time.

Bear in mind that Flutter expects initState to be synchronous. So while dart doesn't prevent you from doing async calls into sync functions, flutter will most likely think your widget is ready when it's not.

I would choose the first style as I can control what happens whilst waiting. It depends on what you want from your app, and I prefer a clear "loading" message rather than not knowing if it is stuck or loading.

And speaking of side effects, you can see what they said about FutureBuilder

A side-effect of this is that providing a new but already-completed future to a FutureBuilder will result in a single frame in the ConnectionState.waiting state. This is because there is no way to synchronously determine that a Future has already completed.

I could just do an empty setState() at the end of my async function.

That would have the effect of queuing the writes such that you'd only see their visual effect after all the asynchronous work had completed. However, the queue wouldn't be reliable. It's possible for something else in your app to cause the widget to rebuild, in which case you'll see the effects of the individual writes.

Or I could wrap each individual assignment into its own setState: use a temporary value for await getCustomValue(); and then assign it to value inside of a setState.

That would ensure that you see visual updates for each incremental state change (assuming there's enough time between the asynchronous tasks to put up a frame).

Which should I go with? Are the intermediate states meaningful to the user? Specifically, do you want to ensure that the visual appears of your app updates as you progress through this sequence of operations? Conversely, do you want to prevent the intermediate states from being shown to the user?

If you want the user to see the intermediate states, you probably want to wrap each write in a setState call. If you want to prevent the intermediate states from being visible, you might want to actually queue the writes and have them all happen atomically at the end of the series of asynchronous operations.

Putting a single setState at the end of the function is something of the worse of both worlds because the intermediate states will be visible arbitrarily depending on what else is happening in your app. In the worst case, the intermediate states could be buggy and hard to reproduce.

Is there a better solution?

Assuming you want to wait for all the operations to complete and then atomically update the state of your object, I'd recommend having the asynchronous object return a representation of all the state changes. Once the asynchronous operation completes, I'd commit all the changes to your state synchronously inside a single setState call. That will have the effect of changing your state atomically and ensuring that the visual appearance of your app is updated.

Another issue to consider is what happens if the widget is removed from the tree while the asynchronous operation is pending. Ideally you'd cancel the asynchronous operation to save resources, but if that's not an option, you might want to check the mounted boolean on your State object before committing the changes.

Got from : 3951#issue

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM