skip to Main Content

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


  1. When you are using an async method, you should check if state of widget is mounted in a StatefulWidget like this:

    () async {
                if (await _connectivity.checkConnectivity() ==
                    ConnectivityResult.none && mounted) {
                  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 {
                  if (mounted) {
                    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);
                      if (mounted) {
                        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));
                    }
                  }
                }
              }
    
    Login or Signup to reply.
  2. It is quite important to check if the widget is mounted or not before performing operations using BuildContext.

    Consider this scenario:

    • You have a screen with two buttons A and B
    • When you press A, After 2 seconds, the current screen is popped off the navigator (Disposed) and replaced by a new screen ScreenA.
    • When you press B, The same thing happens but ScreenB replaces the current one.

    The code for onClickA looks like:

    Future<void> _onClickA() async {
        await Future.delayed(const Duration(seconds: 2));
        await Navigator.of(context).pushReplacement([Your ScreenA route here]);
    }
    

    The code for onClickB is similar:

    Future<void> _onClickB() async {
        await Future.delayed(const Duration(seconds: 2));
        await Navigator.of(context).pushReplacement([Your ScreenB route here]);
    }
    

    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:

    Future<void> _onClickA() async {
        await Future.delayed(const Duration(seconds: 2));
        if (mounted) {
            await Navigator.of(context).pushReplacement([Your ScreenA route here]);
        }
    }
    

    and

    Future<void> _onClickB() async {
        await Future.delayed(const Duration(seconds: 2));
        if (mounted) {
            await Navigator.of(context).pushReplacement([Your ScreenB route here]);
        }
    }
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search