You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

请问如何避免陷入该无限循环?


解决方法

核心问题分析

无限循环的根源在于:

  1. Consumer重建时,FutureBuilder的future参数会重新执行user.getMyUser()
  2. getMyUser内部调用notifyListeners(),再次触发Consumer重建,形成闭环
  3. 原代码中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 04:13:11