不要在Flutter异步操作间使用BuildContext。

6

我写了一个注册函数,但是在Utils.flushBarErrorMessage("No Internet", context);这一行代码上出现了一个警告:不要跨异步间隙使用BuildContexts。 我是Flutter新手,想知道如何使用asyncawait

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);
      }
    }
  }

调用_registration()函数

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

这是我的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));
  }
}

嗯...你能分享一下showFlushbar吗? - lepsch
showFlushbar来自于another_flushbar包。 - user18710300
好的。谢谢。现在我可以测试它了。 - lepsch
你读过这个吗?https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html? - Miftakhul Arzak
@lepsch,你找到解决方案了吗? - user18710300
显示剩余3条评论
3个回答

7
问题在于,在使用await后,每次使用BuildContext都会显示此警告。这种警告发生是因为在await后使用BuildContext可能会在小部件被Disposed(销毁)后发生,此时上下文将不再存在,应用程序甚至可能因此崩溃。请查看官方lint文档

存储BuildContext以供以后使用很容易导致难以诊断的崩溃。异步间隙(asynchronous gaps)隐含地存储BuildContext,并且在编写代码时很容易忽略。

根据官方文档,简单解决方法是需要检查State.mounted。在出现警告的每个位置,代码将如下所示:
      ...
      } else {
        if (mounted) Utils.flushBarErrorMessage("No Internet", context);
      }
      ...

嗨兄弟,希望你一切都好。我已经更新了我的问题,请检查一下。谢谢。 - user18710300
你好。实际上,你应该创建另一个问题,而不是完全改变现有的问题。这样答案就不再与你所要求的相匹配了。如果可能的话,请恢复它。无论如何,不要将上下文传递给 doSignUp,而是将结果作为枚举返回,并在调用方解决它。 - lepsch
我已经将问题回滚到之前的版本,以便与答案保持同步。如果您有其他问题,请创建一个新的问题并让我知道。 - lepsch
我的小部件没有安装变量。 - Scorb
@Scorb 将其更改为有状态小部件 - vladli

4

试试这个:

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

在调用时:

ElevatedButton(
            onPressed: () {
             //make sure your class is of StatefulWidget()
              _registration(context); ///Add context
            },
            child: const Text('SignUp')),

2
同样的问题。由于这不是一个合适的解决方案。 - user18710300
仍然存在相同的问题。 - user18710300
这是正确的解决方案。 - Nagual

3

对于这个警告的最简单解释是:

StatelessWidget

await关键字之前,保留对context使用的引用。

示例:

// 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

只需使用if(mounted)逻辑包装context的使用

例如:

// 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();
        }
    }

这就是我个人的经验。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接