using ChangeNotifierProvider with FutureBuilder issue

99 Views Asked by At

i have a class that is called page1 and within it i have a ChangeNotifierProvider and a consumer as a child which has a FutureBuilder as a child within it, here is the code for it

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

the problem that am facing is whenever the FutureBuilder gets called it calls a future method that invokes a notifyListeners method and this makes the consumer widget to be rebuilt, which eventually enters me into a endless loop, here is the code for model class, which you can find the future method i was speaking of:

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

basically all am looking for is how can i avoid entering this loop?

1

There are 1 best solutions below

2
MendelG On
The problem

I see this problem all the time: Infinite loops in FutureBuilders.

In your case, the problem is that when you call.notifyListener() with:

 user.notifyListeners();

it's going to rebuild the widgets that are listening to the provider, and FutureBuilder is one of the widgets.

The Solution

The solution is to only fetch the data once - in initState.

In your State class, create a nullable variable _fetchUser, and call it in the initState

class _Page1State extends State<Page1> with ChangeNotifier {    
  User? _user;
  Future? _fetchUser;
  ...

   @override
  void initState() {
    super.initState();
    _getAuth();
    _fetchUser = context.read<MyUser>().getMyUser(); // Fetch user data here
  }

And in the FutureBuilder:

ChangeNotifierProvider(
      create: (context) => MyUser(id: _user!.id),
      child: Consumer<MyUser>(
        builder: (context, user, child) => FutureBuilder(
        future: _fetchUser, // -> Use it here
        builder: (context, snapshot) {
          // ...
        }),
    ),

See also