In my Log in Screen, I encounter a problem due to BuildContext within an async block.
When user wlick on "Log in" button (Se connecter in French) : I start an async procedure (check connectivity, check if user credentials are ok…) but I also open "dialog boxes" to display loading indicator, or a sort of a popup (My alert info) to tell user if there was a problem.
I get indication that I shouldn’t use "context" when inside an async clock.
Container(
margin: const EdgeInsets.only(right: 10),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size(20, 45),
backgroundColor: Colors.blue[900],
foregroundColor: Colors.white,
),
onPressed: () async {
if (await _connectivity.checkConnectivity() ==
ConnectivityResult.none) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text(
'Oops... ',
style: TextStyle(fontSize: 22.0),
),
content: const Text(
'Il faut te connecter à Internet !',
style: TextStyle(fontSize: 18.0),
),
actions: [
TextButton(
onPressed: () {
Navigator.pushReplacementNamed(
context, StartingScreen.id);
},
child: const Text(
'OK',
style: TextStyle(fontSize: 18.0),
),
),
],
),
);
} else {
DialogBuilder(context).showLoadingIndicator(
heading: 'Patientez ...',
text: 'Téléchargement des bases',
color: Colors.black45);
String accountVerif =
await uD.checkAccountId(
emailController.text,
passWordController.text);
print('Voici Account Verif : $accountVerif');
if (accountVerif == '') {
await uD.downLoadUserInfo();
final prefs =
await SharedPreferences.getInstance();
prefs.setString('email',
emailController.text.toLowerCase());
prefs.setString(
'passWord', passWordController.text);
DialogBuilder(context).hideOpenDialog();
Navigator.pushReplacementNamed(
context, HomeScreen.id);
} else {
setState(() {
emailController.clear();
passWordController.clear();
});
Navigator.pop(context);
// myAlertInfo(context,
// icone: Icons.warning,
// alerte: 'Oops... ',
// text:
// 'Ce compte n'existe pas, ou le mot de passe est incorrect.',
// bouton1: 'OK',
// function1: () =>
// Navigator.pop(context));
}
}
},
child: Text('Se connecter',
style: TextStyle(fontSize: uD.mediumFont))),
),
How could I review the code to prevent this problem ?
Thanks for your help 🙂
2
Answers
When you are using an async method, you should check if state of widget is
mounted
in aStatefulWidget
like this:It is quite important to check if the widget is mounted or not before performing operations using BuildContext.
Consider this scenario:
The code for onClickA looks like:
The code for onClickB is similar:
The problem arises when the user presses both A and B
When the user presses A, and presses B after a second, both functions get executed. "A" was pressed first so ScreenA appears after 2 seconds but, the previous screen is disposed because we used the pushReplacement method.
But, the _onClickB is still executing. After 2 seconds, it will try to navigate to the ScreenB with the context of a page that has been disposed. Hence, the error.
It is always good to check if the screen is mounted before doing operations using BuildContext. For it, you have to use a StatefulWidget. In a stateful widget’s state class, you get the boolean property
mounted
. You can use it to check if the screen is still not disposed.This is the safer version:
and
Now, even if the user presses both A and B, the navigator does not try to push the ScreenB as the calling screen has been disposed.