如何在StatelessWidget中修复「Do not use BuildContexts across async gaps」错误?
问题
在FireAuth登录流程中,我需要将用户实例存储到ChangeNotifier类型的appState中,执行代码如下:
Provider.of<StateModel>(context, listen: false).signedIn = shokazeUser;
但shokazeUser需要通过await从远程数据库获取,这会导致context可能失效,触发错误:
Do not use BuildContexts across async gaps.
当前使用的是StatelessWidget,无法像StatefulWidget那样通过判断mounted状态处理,请问该如何解决?
我的代码如下:
Future<String?> _authUser(BuildContext context, LoginData data) { debugPrint('Name: ${data.name}, Password: ${data.password}'); return auth .signInWithEmailAndPassword(email: data.name, password: data.password) .then((credential) async { try { final user = credential.user; if (user == null) { return 'Password does not match'; } else { // == Tell the app we just signed in try { final shokazeUser = await shokaze.User.getFromId(user.uid); if (shokazeUser == null) { return "Unable to fetch user from database"; } Provider.of<StateModel>(context, listen: false).signedIn = shokazeUser; } catch (e) { return "Error while fetching user from database: $e"; } return null; } } catch (e) { return 'User not exists'; } }).catchError((e) { if (e.code == 'weak-password') { debugPrint('The password provided is too weak.'); } else if (e.code == 'email-already-in-use') { debugPrint('The account already exists for that email.'); } else { debugPrint(e.toString()); } return Future<String?>.error(e); }); }
解决方案
方法1:使用全局Provider容器获取实例(不依赖BuildContext)
如果StateModel在顶层MultiProvider中注册,可通过全局保存的ProviderContainer直接获取实例,彻底避开BuildContext。
实现步骤:
- 在主入口文件保存容器引用:
void main() { final container = ProviderContainer(); runApp( UncontrolledProviderScope( container: container, child: const MyApp(), ), ); // 将容器存到全局类中 GlobalProviderContainer.instance = container; } class GlobalProviderContainer { static late ProviderContainer instance; }
- 在登录方法中替换原
Provider.of代码:
// 无需context,直接更新状态 GlobalProviderContainer.instance.read<StateModel>().signedIn = shokazeUser;
方法2:将登录逻辑抽离到独立服务类
把FireAuth和用户数据获取逻辑迁移到不依赖Widget的服务类中,通过构造函数注入StateModel,避免使用BuildContext。
示例代码:
class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; final StateModel _stateModel; AuthService(this._stateModel); Future<String?> signIn(LoginData data) async { debugPrint('Name: ${data.name}, Password: ${data.password}'); try { final credential = await _auth.signInWithEmailAndPassword( email: data.name, password: data.password, ); final user = credential.user; if (user == null) { return 'Password does not match'; } try { final shokazeUser = await shokaze.User.getFromId(user.uid); if (shokazeUser == null) { return "Unable to fetch user from database"; } _stateModel.signedIn = shokazeUser; return null; } catch (e) { return "Error while fetching user from database: $e"; } } catch (e) { if (e is FirebaseAuthException) { if (e.code == 'weak-password') { debugPrint('The password provided is too weak.'); } else if (e.code == 'email-already-in-use') { debugPrint('The account already exists for that email.'); } } debugPrint(e.toString()); return e.toString(); } } }
在Widget中调用服务:
final authService = Provider.of<AuthService>(context, listen: false); final error = await authService.signIn(data);
方法3:使用context.mounted(Dart 3.0+)
如果项目使用Dart 3.0及以上版本,StatelessWidget的BuildContext也支持mounted属性,可直接判断Widget是否仍挂载:
final shokazeUser = await shokaze.User.getFromId(user.uid); if (shokazeUser == null) { return "Unable to fetch user from database"; } // 仅在Widget挂载时更新状态 if (context.mounted) { Provider.of<StateModel>(context, listen: false).signedIn = shokazeUser; }
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

