5

I've made a function for registration and I'm getting a warning Do not use BuildContexts across async gaps. on Utils.flushBarErrorMessage("No Internet", context); I'm new to flutter and want to know how to use async and await.

Future _registration() async {
    String name = _nameController.text.trim();
    String email = _emailController.text.trim();
    String password = _passwordController.text.trim();
    String phone = _phoneController.text.trim();

    if (name.isEmpty) {
      Utils.flushBarErrorMessage("Type your name", context);
    } else if (email.isEmpty) {
      Utils.flushBarErrorMessage("Type your email", context);
    } else if (!GetUtils.isEmail(email)) {
      Utils.flushBarErrorMessage("Type valid email address", context);
    } else if (password.isEmpty) {
      Utils.flushBarErrorMessage("Type your password", context);
    } else if (password.length < 6) {
      Utils.flushBarErrorMessage(
          "password can't be less than 6 characters", context);
    } else if (phone.isEmpty) {
      Utils.flushBarErrorMessage("Type your phone", context);
    }
    else {
      var connectivityResult = await (Connectivity().checkConnectivity());
      if (connectivityResult == ConnectivityResult.mobile ||
          connectivityResult == ConnectivityResult.wifi) {
        ApiCall.signUp(name, email, password, phone).then((value) {
          if (value.statusCode == 200) {
            if (json.decode(value.body)['success'] != null) {
              if (json.decode(value.body)["success"]) {
                RegisterResponse registerResponseModel =
                    RegisterResponse.fromJson(json.decode(value.body));
                Navigator.pushNamed(context, VerifyUser.routeName);
                Utils.flushBarErrorMessage(
                    'User Registered Successfully', context);
                if (kDebugMode) {
                  print('User Registered Successfully');
                }
              } else {
                Utils.flushBarErrorMessage(
                    json.decode(value.body)["en_message"], context);
                if (kDebugMode) {
                  print(json.decode(value.body).toString());
                }
              }
            }
          } else {
            Utils.flushBarErrorMessage('invalid data', context);
            if (kDebugMode) {
              print(json.decode(value.body).toString());
            }
          }
        });
      } else {
        Utils.flushBarErrorMessage("No Internet", context);
      }
    }
  }

calling this _registration()

ElevatedButton(
            onPressed: () {
              _registration();
            },
            child: const Text('SignUp')),

Here is my flushBarErrorMessage.

   class Utils {
  static void flushBarErrorMessage(String message, BuildContext context) {
    showFlushbar(
        context: context,
        flushbar: Flushbar(
          forwardAnimationCurve: Curves.decelerate,
          margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
          padding: const EdgeInsets.all(15),
          titleColor: Colors.white,
          duration: const Duration(seconds: 3),
          borderRadius: BorderRadius.circular(10),
          reverseAnimationCurve: Curves.easeInOut,
          icon: const Icon(
            Icons.error,
            size: 28,
            color: Colors.white,
          ),
          flushbarPosition: FlushbarPosition.TOP,
          positionOffset: 20,
          message: message,
          backgroundColor: Colors.red,
        )..show(context));
  }
}
lepsch
  • 8,927
  • 5
  • 24
  • 44

3 Answers3

6

The problem is that after an await, every use of the BuildContext will show this warning. This warning happens because using a BuildContext after an await could happen after the widget is disposed of. This way, the context wouldn't exist anymore and the app could even crash because of this. Check out the official lint documentation:

Storing BuildContext for later usage can easily lead to difficult-to-diagnose crashes. Asynchronous gaps are implicitly storing BuildContext and are some of the easiest to overlook when writing code.

The easy solution, from the official docs, is the need to check for State.mounted. The code would look something like this on every place the warning shows up:

      ...
      } else {
        if (mounted) Utils.flushBarErrorMessage("No Internet", context);
      }
      ...
lepsch
  • 8,927
  • 5
  • 24
  • 44
  • Hi bro, hope you are doing good. I've updated my question kindly check it. Thanks –  Jul 12 '22 at 09:43
  • Hi there. You actually should create another question instead of changing the existing one completely. This way the answers don’t match anymore what you’re asking for. If possible revert it. Anyway, instead of passing the context to `doSignUp` return the result as an enum for example and resolve it on the caller side. – lepsch Jul 12 '22 at 11:51
  • I've rolled back the question to the previous one to keep it in sync with the answers. If you have another question please create a new one and let me know. – lepsch Jul 12 '22 at 14:44
  • My widget does not have a mounted variable. – Scorb Dec 31 '22 at 02:47
  • @Scorb change it to statefull widget – vladli Jan 25 '23 at 22:48
3

Try this:

///Add context
Future _registration(BuildContext context) async {
...
if(!mounted) return;
 Navigator.pushNamed(context, VerifyUser.routeName);
...
}

When calling:

ElevatedButton(
            onPressed: () {
             //make sure your class is of StatefulWidget()
              _registration(context); ///Add context
            },
            child: const Text('SignUp')),
Davis
  • 1,219
  • 2
  • 8
  • 17
1

The simplest answer for this warning is:

StatelessWidget

keep a reference of the context usage before the await keyword.

example:

// navigation
onPressed: () async {
    final router = GoRouter.of(context);

    final result = await [
        Permission.location,
        Permission.locationAlways,
        Permission.locationWhenInUse
    ].request();

    if (...) {
        router.go(AppRouter.dashboard);
    } else {
        router.go(AppRouter.askForCustomLocation);
    }

// cubit
onPressed: () async {

    final appSettingsCubit = BlocProvider.of<AppSettingsCubit>(context);

    final result = await [
        Permission.location,
        Permission.locationAlways,
        Permission.locationWhenInUse
    ].request();

    if (...) {
        appSettingsCubit.locationProptAsked();
    } else {
        appSettingsCubit.locationProptAsked();
    }

StatefullWidget

just wrap the context usage with a if(mounted) logic

example:

// navigation
onPressed: () async {    
    final result = await [
        Permission.location,
        Permission.locationAlways,
        Permission.locationWhenInUse
    ].request();

    if (...) {
        if(mounted) {
            router.go(AppRouter.dashboard);
        }
    } else {
        if(mounted) {
            router.go(AppRouter.dashboard);
        }
    }

// cubit
onPressed: () async {
    final result = await [
        Permission.location,
        Permission.locationAlways,
        Permission.locationWhenInUse
    ].request();

    if (...) {
        if(mounted) {
            BlocProvider.of<AppSettingsCubit>(context).locationProptAsked();
        }
    } else {
        if(mounted) {
            BlocProvider.of<AppSettingsCubit>(context).locationProptAsked();
        }
    }

That's all from my experience.