Kshitij Dhakal
Kshitij Dhakal

Reputation: 844

How to safely call setState function from async function in flutter?

I have a download button in my flutter application that will show Text("download") before button is pressed CircularProgressIndicator() when file is downloading, and Text("Open") when file is downloaded.

_FileStatus status = _FileStatus.EMPTY;

RaisedButton raisedButton() {
  var child;
  if (status == _FileStatus.EMPTY) {
    child = Text("download");
  } else if (status == _FileStatus.DOWNLOADING) {
    child = CircularProgressIndicator();
  } else {
    child = Text("Open");
  }
  return RaisedButton(
    child: child,
    onPressed: () {
      if (status == _FileStatus.EMPTY) {
        setState(() {
          status = _FileStatus.DOWNLOADING;
        });
        download().then((bool isDownloaded) {
          if (isDownloaded) {
            //setState status=>Downloaded
          } else {
            //setState status=>Empty
          }
        });
      } else {
        print("Open");
      }
    },
  );
}

Future<bool> download() async {
  var response = await Future<bool>.delayed(Duration(seconds: 5), () {
    print("Downloading ...");
    return true;
  });
  if (response) {
    print("Downloaded");
    return true;
  } else {
    print("Download Failed!");
    return false;
  }
}

But this button lies in second page of application. So one might press download button and then go back to first page. Normally if I press download button and then wait for it to complete then setState will update status to DOWNLOADED; no problem whatsoever. But if I press download button and then go back, then download() will run in background and when it completes it tries to run one of the setState methods that I've commented out. This either gives error, or says memory leak problem.

I've tried to solve this problem by using isLoaded variable.

@override
void initState() {
  isLoaded = true;
  super.initState();
}
@override
void dispose() {
  isLoaded = false;
  super.dispose();
}

and then checking if isLoaded is true before calling setState method.

if (isLoaded) {
  setState(() {
    status = _FileStatus.DOWNLOADING;
  });
}

But this feels like hacky method, and I know there must be a better way to tackle this. I've searched to other similar question but couldn't quite find what I was looking for.

Upvotes: 1

Views: 1002

Answers (1)

Igor Kharakhordin
Igor Kharakhordin

Reputation: 9933

Just add a condition to check whether your state is mounted or not, and when it's not - do not call setState:

          download().then((bool isDownloaded) {
            // Checking if widget is mounted
            if (!mounted) {
              return;
            }

            ...

Upvotes: 2

Related Questions