3
votes

I'm trying to update my uid in a provider just after checking whether a user is logged. When I do that, throws an error when building widgets even though the app does not crash. Here is the code:

class HandleAuth extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var user = Provider.of<FirebaseUser>(context);
    if (user != null) {
      print('user.uid is ${user.uid}');
      final loggedUserInfo = Provider.of<LoggedUserInfo>(context, listen: false);
      loggedUserInfo.updateUserInfo(user.uid);
      print('first scan screen user: ${loggedUserInfo.userUid}');

    }
    return (user == null)
            ? WelcomeNewUserScreen()
            : ServicesAroundMe();
  }
}

And here is the provider:

class LoggedUserInfo with ChangeNotifier {
  String _uid;

  String get userUid {
    return _uid;
  }

  void updateUserInfo(String updatedUid) {
    _uid = updatedUid;
    print('updated uid is $_uid');
    notifyListeners();
  }

}

It throws this error:

This ListenableProvider widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: ListenableProvider

1

1 Answers

3
votes

You have to bear in mind that each time you call the method updateUserInfo, the notifyListeners() is triggered which tries to rebuild dirty widgets. Provider.of<T>(context) without the argument listen: false also does the same thing. Hence 2 rebuild trigger are called which cause the error.

When working with a provider, it is advisable to use a stream.

To make your code more scalable, create a custom class for your user and Either use ChangeNotifier or provider and streams.

For example;

class User {
  final String uid;
  final String displayName;
  User({ @required this.uid, this.displayName });
}

class Auth {

   User _firebaseUserMapper( FirebaseUser user) {
       if(user == null){
           return null;
       }
       return User(uid: user.uid, displayName: user.displayName);
   }

   Stream<User> get onAuthStateChange {
       return Firebase.instance.onAuthStateChanged.map(_firebaseUserMapper);
   }


}

In your page screen, you use like bellow

class HandleAuth extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<Auth>(context, listen: false);
    return StreamBuilder<User>(
         stream: auth.onAuthStateChanged,
         builder: (BuildContext context, AsyncSnapshot snapshot) {
             if( snapshot.connectionState == ConnectionState.active) {
                   User user = snapshot.data;
                   if (user == null ){
                       return WelcomeNewUserScreen();
                   }
                   return Provider<User>.value(
                        value: user,
                        child: ServicesAroundMe(),
                   );
              }
              return Scaffold(
                  body: Center(
                      child: CircularProgressIndicator();
                  ),
              );
         }
    );
}

The stream will forever listen for a currentUser/newUser and navigate to the appropriate page.