187

I'm a looking for a way to load async data on InitState method, I need some data before build method runs. I'm using a GoogleAuth code, and I need to execute build method 'till a Stream runs.

My initState method is:

 @override
  void initState () {
    super.initState();
    _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account)     {
      setState(() {
        _currentUser = account;
      });
    });
    _googleSignIn.signInSilently();
  }

I will appreciate any feedback.

Joseph Arriaza
  • 12,014
  • 21
  • 44
  • 63

17 Answers17

139

You can create an async method and call it inside your initState

@override
void initState () {
  super.initState();
  WidgetsBinding.instance.addPostFrameCallback((_){
    _asyncMethod();
  });
        
}

_asyncMethod() async {
  _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account)     {
    setState(() {
      _currentUser = account;
    });
  });
  _googleSignIn.signInSilently();
}
Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
diegoveloper
  • 93,875
  • 20
  • 236
  • 194
  • 7
    Shouldn't change anything, unless his call is sync and instant. But then there's no async problem – Rémi Rousselet Aug 17 '18 at 19:04
  • 36
    Ini this case `build` executed BEFORE `_asyncMethod` completed! – BambinoUA Jan 15 '20 at 12:38
  • 6
    @diegoveloper what is the purpose of using `WidgetsBinding.instance.addPostFrameCallback` instead of @Nae answer? – BIS Tech Jan 23 '20 at 00:02
  • 5
    It is wrong to call setState() when you are initing the state, this causes refresh on page after page is loaded because you're calling an async method in initState. The soulution is only FutureBuilder(). – Mohsen Emami Aug 04 '20 at 06:13
  • no, it's fine, because I'm using WidgetsBinding.instance.addPostFrameCallback – diegoveloper Aug 04 '20 at 06:15
  • Is there any difference with `SchedulerBinding`? – Daniel Gomez Rico Dec 30 '20 at 20:49
  • 2
    @BISTech the reason to use the `WidgetsBinding.instance.addPostFrameCallback` is because the async method calls `setState()` without the callback the state may be attempted to be changed before the `initState()` method completes which is forbidden. – AutoM8R Oct 20 '22 at 04:27
  • @AutoM8R No it's not right? Imagine I have a Future function it just returns a value without await. Ex: `Future _asyncMethod() async { return model; };` If I write two print statements inside `addPostFrameCallback` and `_asyncMethod().then` method, first print addPostFrameCallback's print and then asynMethod print. – BIS Tech Oct 20 '22 at 05:06
  • Full Code: ``` SchedulerBinding.instance.addPostFrameCallback((_) { print('called : addPostFrameCallback'); }); db.asyncFuntion().then((value){ print('called : asyncFuntion'); }); ``` – BIS Tech Oct 20 '22 at 05:08
  • @BISTech essentially `initState()` completes, build completes, frame drawn, callback occurs then you can await for an async method to complete and use `setState()` safely. What you're saying does not negate what I am saying. The order of execution which matters is the fact the build has completed, not the order of the async methods themselves. No async method with an embedded `setState()` should run without knowing the build has completed, that's the point of this callback. – AutoM8R Oct 20 '22 at 18:36
107

As of now using .then notation seems to work:

  // ...
  @override
  initState() {
    super.initState();
    myAsyncFunction
    // as suggested in the comment
    // .whenComplete() {
    // or
      .then((result) {
    print("result: $result");
    setState(() {});
    });
  }
  //...
Nae
  • 14,209
  • 7
  • 52
  • 79
  • 41
    if your async function not return anything, use `whenComplete` instead. – Ahmad Jamil Al Rasyid Aug 31 '19 at 12:19
  • 3
    @Aravin Personally I prefer streams as much as possible. But this 'works' for demonstrative purposes. I'd very much doubt that this would be the _right_ way. – Nae Oct 03 '20 at 18:30
  • I get "Unhandled Exception: setState() called after dispose()". – Eradicatore Apr 16 '21 at 19:04
  • 2
    @Eradicatore It sounds like the widget is disposed of before your async function finishes. – Nae Apr 18 '21 at 09:47
  • Yea, my fault. I figured it out. So I had some code in place to avoid calling my cloud init functions twice in a row too fast, but didn't realize that flutter was starting up my stateful widget twice rapidly at every boot. (see https://stackoverflow.com/questions/56145378/why-is-initstate-called-twice). I was trying to avoid waste calls to my cloud functions. So my widget tree really was disposed for the first call to my stateful widget. – Eradicatore Apr 18 '21 at 11:27
  • @Nae What's the actual difference between using streams vs using setState? – Shourya Shikhar Nov 24 '21 at 03:55
  • 1
    @ShouryaShikhar Streams are the de facto way to build widgets as they come, the above answer is just a hacky way to load asynchronously, but managing the lifetime of the widget etc. is much harder. – Nae Nov 24 '21 at 08:12
  • Thank's a lot, work's well with progress indicator super.initState(); UpDateList() //.whenComplete(){ .then((result) { //print("result: $result"); setState(() {isLoading=result;}); }); – AndroLogiciels Dec 25 '22 at 14:50
68

Method 1 : You can use StreamBuilder to do this. This will run the builder method whenever the data in stream changes.

Below is a code snippet from one of my sample projects:

StreamBuilder<List<Content>> _getContentsList(BuildContext context) {
    final BlocProvider blocProvider = BlocProvider.of(context);
    int page = 1;
    return StreamBuilder<List<Content>>(
        stream: blocProvider.contentBloc.contents,
        initialData: [],
        builder: (context, snapshot) {
          if (snapshot.data.isNotEmpty) {
            return ListView.builder(itemBuilder: (context, index) {
              if (index < snapshot.data.length) {
                return ContentBox(content: snapshot.data.elementAt(index));
              } else if (index / 5 == page) {
                page++;
                blocProvider.contentBloc.index.add(index);
              }
            });
          } else {
            return Center(
              child: CircularProgressIndicator(),
            );
          }
        });
  }

In the above code StreamBuilder listens for any change in contents, initially its an empty array and shows the CircularProgressIndicator. Once I make API call the data fetched is added to contents array, which will run the builder method.

When the user scrolls down, more content is fetched and added to contents array which will again run builder method.

In your case only initial loading will be required. But this provides you an option to display something else on the screen till the data is fetched.

Hope this is helpful.

EDIT:

In your case I am guessing it will look something like shown below:

StreamBuilder<List<Content>>(
        stream: account, // stream data to listen for change
        builder: (context, snapshot) {
            if(account != null) {
                return _googleSignIn.signInSilently();
            } else {
                // show loader or animation
            }
        });

Method 2: Another method would be to create an async method and call it from you initState() method like shown below:

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

  void asyncMethod() async {
    await asyncCall1();
    await asyncCall2();
    // ....
  }
Thanthu
  • 4,399
  • 34
  • 43
29

Create anonymous function inside initState like this:

@override
void initState() {
  super.initState();
  
  // Create anonymous function:
  () async {
    await _performYourTask();
    setState(() {
     // Update your UI with the desired changes. 
    });
  } ();
}
iDecode
  • 22,623
  • 19
  • 99
  • 186
  • 6
    Please have some courage to mention the reason behind downvoting. – iDecode Dec 18 '20 at 19:04
  • 2
    How's this different than calling `_performYourTask();` directly? How does wrapping the async function with another async function that is not awaited help? – Nae Dec 22 '20 at 21:09
  • 1
    In the above anonymous function you can call `setState` after `_performYourTask` is done (which you surely can't do in the `initState` directly) – iDecode Dec 23 '20 at 09:07
  • So the extra wrap is reserved for queueing `setState` call after the wanted async method is _finished_. I'd then suggest adding that line after the `await`. Because right now it doesn't seem to be doing anything different. – Nae Dec 23 '20 at 09:44
8

Previous Answer!!

You can set a Boolean value like loaded and set it to true in your listen function and make your build function return your data when loaded is set to true otherwise just throw a CircularProgressIndicator

Edited -- I would not suggest calling setState in a method you call in initState. If the widget is not mounted while the setState is called (as the async operation completes) an error will be reported. I suggest you use a package after_layout

Take a look at this answer for better understanding setState in initState : https://stackoverflow.com/a/53373017/9206337

This post will give you an idea to know when the app finishes the build method. So that you can wait for your async method to setState after widget is mounted : https://stackoverflow.com/a/51273797/9206337

Fardeen Khan
  • 770
  • 1
  • 9
  • 20
8
  @override
  void initState() {
    super.initState();
    asyncInitState(); // async is not allowed on initState() directly
  }

  void asyncInitState() async {
    await yourAsyncCalls();
  }
Gorges
  • 246
  • 5
  • 13
  • While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – β.εηοιτ.βε May 23 '20 at 22:25
  • 3
    No, doesnt work. put a print in initState and build and the code inside asyncInitState executes after build – chitgoks Aug 01 '21 at 09:11
  • asyncInitState() executes only when widget is build. Try to use yourAsyncCalls().then((value)=>'Your code here') inside in initState. – NepoKale Aug 29 '21 at 19:16
  • 1
    this worked, thank you! It's very likely other solutions also work but would be curious to know why the more complicated ones like using FutureBuilder would be preferred to this – Jake Boomgaarden Feb 17 '22 at 11:40
  • welcome. For me, the simpler, the better. – Gorges Feb 19 '22 at 00:29
7

You can create an async method and call it inside your initState

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

    asyncMethod(); ///initiate your method here
  }

Future<void> asyncMethod async{
 
 await ///write your method body here
}
Supun Dewapriya
  • 695
  • 11
  • 13
5

Per documentation at https://pub.dev/packages/provider

initState() {
  super.initState();
  Future.microtask(() =>
    context.read<MyNotifier>(context).fetchSomething(someValue);
  );
}
4

initState() and build cannot be async; but in these, you can call a function that is async without waiting for that function.

Nae
  • 14,209
  • 7
  • 52
  • 79
Muhammad Umair Saqib
  • 1,287
  • 1
  • 9
  • 20
4

How about this?

@override
void initState() {
 //you are not allowed to add async modifier to initState
 Future.delayed(Duration.zero,() async {
  //your async 'await' codes goes here
 });
 super.initState();
}
3

Sample code:

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

    asyncOperation().then((val) {
      setState(() {});
      print("success");
    }).catchError((error, stackTrace) {
      print("outer: $error");
    });

//or

    asyncOperation().whenComplete(() {
      setState(() {});
      print("success");
    }).catchError((error, stackTrace) {
      print("outer: $error");
    });
  }

  Future<void> asyncOperation() async {
    await ... ;
  }
live-love
  • 48,840
  • 22
  • 240
  • 204
2

As loading or waiting for initial state is a (generally) aone off event FutureBuilder would seem to be a good option as it blocks once on an async method; where the async method could be the loading of json config, login etc. There is an post on it [here] in stack.(Flutter StreamBuilder vs FutureBuilder)

Mark Parris
  • 193
  • 9
1
@override
  void initState() {
    super.initState();
     _userStorage.getCurrentUser().then((user) {
      setState(() {
        if (user.isAuthenticated) {
          Timer.run(() {
            redirectTo();
          });
        }
      });
    });
  }

 void redirectTo() {
    Navigator.push(context,
        MaterialPageRoute(builder: (BuildContext context) => new ShopOrders()));
  }
Aathi
  • 2,599
  • 2
  • 19
  • 16
0

I would strongly suggest using a FutureBuilder. It takes care of executing the async function and building the widget according to the result! Here's a link to a short intro video and the documentation.

Code Example:

  Future<void> initControllers() async {
    for (var filePath in widget.videoFilePaths) {
      var val = VideoPlayerController.file(File(filePath));
      await val.initialize();
      controllers.add(val);
    }
  }


  @override
  Widget build(BuildContext context) {
    FutureBuilder(
          future: initControllers(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done) {
              return YourWidget();
            } else {
              return const ProgressIndicator();
            }
          },
        ));}
aeaevo
  • 36
  • 4
0

Tried all suggestions, none would keep my build from starting after the async method that I need in initState() finish, except one: the trick of having a a bool variable in the State class (let's call it _isDataLoaded) that is initialized to false upon definition, set to true inside a setState() that is invoked when the async function finishes inside initState(). In the build, condition a CircleProcessIndicator() or your Widget depending on the value of this variable.

I know it's dirty because it could break the build, but honestly nothing else that would make more sense -such as running super.initState() upon completion of the async function- has worked for me.

eva
  • 59
  • 3
-1

I came here because I needed to fetch some files from FTP on program start. My project is a flutter desktop application. The main thread download the last file added to the FTP server, decrypts it and displays the encrypted content, this method is called from initState(). I wanted to have all the other files downloaded in background after the GUI shows up.

None of the above mentioned methods worked. Constructing an Isolate is relatively complex.

The easy way was to use the "compute" method:

  1. move the method downloading all files from the FTP out of the class.
  2. make it an int function with an int parameter (I do not use the int parameter or the result)
  3. call it from the initState() method

In that way, the GUI shows and the program downloads the files in background.

  void initState() {
    super.initState();
    _retrieveFileList(); // this gets the first file and displays it
    compute(_backgroundDownloader, 0); // this gets all the other files so that they are available in the local directory
  }

int _backgroundDownloader(int value) {
  var i = 0;
  new Directory('data').createSync();
  FTPClient ftpClient = FTPClient('www.guckguck.de',
      user: 'maxmusterman', pass: 'maxmusterpasswort');
  try {
    ftpClient.connect();
    var directoryContent = ftpClient.listDirectoryContent();
    // .. here, fileNames list is reconstructed from the directoryContent

    for (i = 0; i < fileNames.length; i++) {
      var dirName = "";
      if (Platform.isLinux)
        dirName = 'data/';
      else
        dirName = r'data\';
      var filePath = dirName + fileNames[i];
      var myDataFile = new File(filePath);
      if (!myDataFile.existsSync())
        ftpClient.downloadFile(fileNames[i], File(filePath));
    }
  } catch (err) {
    throw (err);
  } finally {
    ftpClient.disconnect();
  }
  return i;
amirzolal
  • 168
  • 1
  • 9
-1

I have used timer in initState

Timer timer;

@override
void initState() {
  super.initState();
  timer = new Timer.periodic(new Duration(seconds: 1), (Timer timer) async {
      await this.getUserVerificationInfo();
  });
}

@override
void dispose() {
    super.dispose();
    timer.cancel();
}

getUserVerificationInfo() async {
   await someAsyncFunc();
   timer.cancle();
}
Saad Ahmed
  • 700
  • 1
  • 8
  • 15