0

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?

sidali
  • 7
  • 5

1 Answers1

0
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

MendelG
  • 14,885
  • 4
  • 25
  • 52
  • the problem with this solution is that you are calling the future method outside the scope of the consumer, which will cause a problem, and even if that was not the case, i believe that it will have some short comings, as am counting on the consumer to be rebuilt whenever the data get changed, which wont be possible using this approach as you are calling the future which updates the data inside the initState, which will triggers once. – sidali Aug 05 '23 at 08:55
  • the type of behavior am trying to achieve is somewhat like this : ChangeNotifireProvider -> Consumer(so the splash screen gets rebuilt when user info get updated) -> FutureBuilder (calls the method that fetches the user info from the backend, and notify listeners, therefore makes the consumer widget rebuild) -> build the rest of the widget tree according to the response. but doing it the way i did leads to a infinite loop, i guess this login in of it self is problematic, and i should try a different approach – sidali Aug 05 '23 at 11:33