ChangeNotifierProvider与FutureBuilder配合引发无限循环问题求解
问题:ChangeNotifier与FutureBuilder嵌套导致无限循环
我有一个名为Page1的StatefulWidget类,内部使用ChangeNotifierProvider,其子组件Consumer中嵌套了FutureBuilder,代码如下:
class Page1 extends StatefulWidget { const Page1({super.key}); @override State<Page1> createState() => _Page1State(); } class _Page1State extends State<Page1> with ChangeNotifier { User? _user; Future _getAuth() async { setState(() { _user = supabaseClient.auth.currentUser; }); supabaseClient.auth.onAuthStateChange.listen((event) { if (mounted) { setState(() { _user = event.session?.user; }); } }); } @override void initState() { _getAuth(); super.initState(); } @override Widget build(BuildContext context) { return _user == null ? const LogIn() : ChangeNotifierProvider( create: (context) => MyUser(id: _user!.id), child: Consumer<MyUser>( builder: (context, user, child) => FutureBuilder( future: user.getMyUser(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.none) { return const Scaffold( body: Text('NO INTERNET CONNECTION')); } else if (snapshot.connectionState == ConnectionState.waiting) { return const Scaffold( body: Center(child: CircularProgressIndicator())); } else if (snapshot.hasError) { return Scaffold( body: Center(child: Text('${snapshot.error}')), ); } else { if (user.bmr != null) { user.notifyListeners(); return user.bmr == null ? const PersonalInfo() : const HomePage(); } else { return const Scaffold( body: Center( child: Text( 'this is the last else statement, why is it happening'), ), ); } } }), ), ); } }
当前遇到的问题是:每当FutureBuilder被调用时,它会调用MyUser类的getMyUser方法,该方法会触发notifyListeners,导致Consumer组件重建,最终陷入无限循环。以下是MyUser模型类的代码:
class MyUser extends ChangeNotifier { String id; String? firstName; String? lastName; int? age; int? height; int? weight; int? gender; int? goal; int? activityLevel; double? bmr; double? caloriesLevel; double? caloriesTarget; MyUser({ required this.id, this.firstName, this.lastName, this.age, this.height, this.weight, this.gender, this.goal, this.activityLevel, this.bmr, this.caloriesLevel, this.caloriesTarget, }); factory MyUser.fromJson(Map<String, dynamic> json) { return MyUser( id: json['id'], firstName: json['first_name'] as String?, lastName: json['last_name'] as String?, age: json['age'] as int?, height: json['height'] as int?, weight: json['weight'] as int?, gender: json['gender'] as int?, goal: json['goal'] as int?, activityLevel: json['activity_level'] as int?, bmr: json['bmr'] as double?, caloriesLevel: json['calories_level'] as double?, caloriesTarget: json['calories_target'] as double?, ); } Future<void> getMyUser() async { Map<String, dynamic> response = await supabaseClient.from('user_info').select().eq('id', id).single(); MyUser updatedUser = MyUser.fromJson(response); firstName = updatedUser.firstName; lastName = updatedUser.lastName; age = updatedUser.age; height = updatedUser.height; weight = updatedUser.weight; gender = updatedUser.gender; goal = updatedUser.goal; activityLevel = updatedUser.activityLevel; bmr = updatedUser.bmr; caloriesLevel = updatedUser.caloriesLevel; caloriesTarget = updatedUser.caloriesTarget; notifyListeners(); } }
请问如何避免陷入该无限循环?
解决方法
核心问题分析
无限循环的根源在于:
- Consumer重建时,FutureBuilder的
future参数会重新执行user.getMyUser() getMyUser内部调用notifyListeners(),再次触发Consumer重建,形成闭环- 原代码中FutureBuilder的成功分支还额外调用了一次
notifyListeners(),加剧了循环
1. 缓存Future对象,避免重复执行
将user.getMyUser()的返回Future存储在State中,避免每次build都创建新的Future实例:
修改_Page1State代码:
class _Page1State extends State<Page1> { User? _user; Future<void>? _fetchUserFuture; // 缓存Future对象 Future _getAuth() async { setState(() { _user = supabaseClient.auth.currentUser; }); supabaseClient.auth.onAuthStateChange.listen((event) { if (mounted) { setState(() { _user = event.session?.user; // 用户状态变化时,重新获取数据并更新Future if (_user != null) { final myUser = Provider.of<MyUser>(context, listen: false); _fetchUserFuture = myUser.getMyUser(); } }); } }); } @override void initState() { _getAuth(); super.initState(); } @override Widget build(BuildContext context) { return _user == null ? const LogIn() : ChangeNotifierProvider( create: (context) { final myUser = MyUser(id: _user!.id); // 初始化Future _fetchUserFuture = myUser.getMyUser(); return myUser; }, child: Consumer<MyUser>( builder: (context, user, child) => FutureBuilder( future: _fetchUserFuture, // 使用缓存的Future builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Scaffold( body: Center(child: CircularProgressIndicator())); } else if (snapshot.hasError) { return Scaffold( body: Center(child: Text('${snapshot.error}')), ); } else { // 移除多余的notifyListeners调用 return user.bmr != null ? const HomePage() : const PersonalInfo(); } }, ), ), ); } }
2. 移除冗余的notifyListeners调用
原代码中FutureBuilder成功分支里的user.notifyListeners()完全多余——getMyUser方法内部已经调用过该方法,重复调用只会触发不必要的重建,直接删除即可。
3. 优化MyUser的数据更新逻辑(可选)
在getMyUser中先对比数据是否变化,再决定是否调用notifyListeners,减少不必要的重建:
Future<void> getMyUser() async { Map<String, dynamic> response = await supabaseClient.from('user_info').select().eq('id', id).single(); MyUser updatedUser = MyUser.fromJson(response); // 对比所有字段是否变化 bool hasChanged = firstName != updatedUser.firstName || lastName != updatedUser.lastName || age != updatedUser.age || height != updatedUser.height || weight != updatedUser.weight || gender != updatedUser.gender || goal != updatedUser.goal || activityLevel != updatedUser.activityLevel || bmr != updatedUser.bmr || caloriesLevel != updatedUser.caloriesLevel || caloriesTarget != updatedUser.caloriesTarget; if (hasChanged) { firstName = updatedUser.firstName; lastName = updatedUser.lastName; age = updatedUser.age; height = updatedUser.height; weight = updatedUser.weight; gender = updatedUser.gender; goal = updatedUser.goal; activityLevel = updatedUser.activityLevel; bmr = updatedUser.bmr; caloriesLevel = updatedUser.caloriesLevel; caloriesTarget = updatedUser.caloriesTarget; notifyListeners(); } }
4. 重构为ChangeNotifier内部管理状态(推荐)
将数据加载、错误状态移到MyUser内部,彻底摆脱FutureBuilder的依赖:
首先修改MyUser类,添加加载和错误状态:
class MyUser extends ChangeNotifier { String id; String? firstName; String? lastName; int? age; int? height; int? weight; int? gender; int? goal; int? activityLevel; double? bmr; double? caloriesLevel; double? caloriesTarget; // 添加状态管理字段 bool isLoading = true; String? errorMessage; MyUser({ required this.id, this.firstName, this.lastName, this.age, this.height, this.weight, this.gender, this.goal, this.activityLevel, this.bmr, this.caloriesLevel, this.caloriesTarget, }) { // 初始化时自动加载数据 _loadUserData(); } Future<void> _loadUserData() async { try { isLoading = true; notifyListeners(); // 通知加载状态变化 Map<String, dynamic> response = await supabaseClient.from('user_info').select().eq('id', id).single(); MyUser updatedUser = MyUser.fromJson(response); firstName = updatedUser.firstName; lastName = updatedUser.lastName; age = updatedUser.age; height = updatedUser.height; weight = updatedUser.weight; gender = updatedUser.gender; goal = updatedUser.goal; activityLevel = updatedUser.activityLevel; bmr = updatedUser.bmr; caloriesLevel = updatedUser.caloriesLevel; caloriesTarget = updatedUser.caloriesTarget; errorMessage = null; } catch (e) { errorMessage = e.toString(); } finally { isLoading = false; notifyListeners(); // 通知状态更新完成 } } // 提供刷新方法,用于用户状态变化时重新加载 Future<void> refresh() async { await _loadUserData(); } factory MyUser.fromJson(Map<String, dynamic> json) { return MyUser( id: json['id'], firstName: json['first_name'] as String?, lastName: json['last_name'] as String?, age: json['age'] as int?, height: json['height'] as int?, weight: json['weight'] as int?, gender: json['gender'] as int?, goal: json['goal'] as int?, activityLevel: json['activity_level'] as int?, bmr: json['bmr'] as double?, caloriesLevel: json['calories_level'] as double?, caloriesTarget: json['calories_target'] as double?, ); } }
然后简化Page1的代码:
class _Page1State extends State<Page1> { User? _user; Future _getAuth() async { setState(() { _user = supabaseClient.auth.currentUser; }); supabaseClient.auth.onAuthStateChange.listen((event) { if (mounted) { setState(() { _user = event.session?.user; // 用户状态变化时刷新数据 if (_user != null) { final myUser = Provider.of<MyUser>(context, listen: false); myUser.refresh(); } }); } }); } @override void initState() { _getAuth(); super.initState(); } @override Widget build(BuildContext context) { return _user == null ? const LogIn() : ChangeNotifierProvider( create: (context) => MyUser(id: _user!.id), child: Consumer<MyUser>( builder: (context, user, child) { if (user.isLoading) { return const Scaffold( body: Center(child: CircularProgressIndicator())); } else if (user.errorMessage != null) { return Scaffold( body: Center(child: Text(user.errorMessage!))); } else { return user.bmr != null ? const HomePage() : const PersonalInfo(); } }, ), ); } }
这种方式让状态管理更集中,完全避免了FutureBuilder带来的重复执行问题,代码结构也更清晰。
内容的提问来源于stack exchange,提问作者user18222209
相关产品推荐
相关产品推荐

