How to Use BuildContext with Async Safely

If you’ve ever received the DON'T use BuildContext across asynchronous gaps. message in Flutter you’re in good company. I keep getting it, fixing it, then forgetting what I did when I get it next time. The solution is very simple, don’t do that in stateless widgets and always check if the widget is mounted. It is made slightly more complicated by the fact that the docs on the linter error are either wrong or out of date.

Build Context The Wrong Way

void onNextButtonTapped(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  Navigator.of(context).pop();
}

Here’s why this is bad. This is a button handler called from the onPress method of some button in the UI. While this method is being evaluated other methods could be called because this method is async. If the widget also had a different button on it, say a close button, that button could call Navigator.of(context).pop() while the onButtonTapped method is still being evaluated. In that case the result of the Navigator call in this method would be an error because the widget the BuildContext depends on would already be gone.

Build Context The Right Way

void onNextButtonTapped(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  if (mounted) {
    Navigator.of(context).pop();
  }
}

This works, and passes the lint check, because if the widget has been invalidated before the Navigator call is reached it won’t try to use it. Note that this only works in a StatefulWidget, not in a StatelessWidget. If you’re trying to solve the problem in a StatelessWidget the solution is to convert it to a StatefulWidget and then use the mounted check as above. There are other suggestions in various answers on Stack Overflow suggesting that you save the Navigator before any await or other asynchronous calls but I don’t think that solves the underlying problem, it just means the linter won’t notice it.

Leave a Reply