Search code examples
flutterdartgoogle-cloud-firestoregoogle-signinflutter-futurebuilder

Fetch data from firestore after Sign In before showing HomeScreen


When a User sign up for the first time, i want that he gets a own firestore document with some data. This data I want to show on the homescreen but I get an error that the data is not there yet. After hot reload the data is there so the problem is that the homescreen is shown before the data is fetched from firestore although I use a FutureBuilder. I only got this problem when a user signs in for the first time.

class GoogleSignInProvider extends ChangeNotifier {
  final googleSignIn = GoogleSignIn();

  GoogleSignInAccount _user;

  GoogleSignInAccount get user => _user;

  Future googleLogin() async {
    try {
      final googleUser = await googleSignIn.signIn();
      if (googleUser == null) return;
      _user = googleUser;

      final googleAuth = await googleUser.authentication;

      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      await FirebaseAuth.instance.signInWithCredential(credential);

      await getUserData();

    } catch (e) {
      print(e.toString());
    }

    notifyListeners();

  }

  Future getUserData() async {

    final myUser = FirebaseAuth.instance.currentUser;
    bool userExist = await checkIfUserExists(myUser.uid);

    if (userExist == false) {
      print('User dont exist');
      await FirebaseFirestore.instance.collection('users').doc(myUser.uid).set({
        "email": myUser.email,
        "plans": [],
        "userScore": "100",
      });
    } else {
      print('User exist');
    }


    /// Save userData from firestore in a Helper class which is shown on the homescreen

    var userData = FirebaseFirestore.instance.collection('users').doc(myUser.uid);

    return FutureBuilder(
        future: userData.get(),
        builder: (context, userDataSnapshot) {
          if (userDataSnapshot.data == ConnectionState.done) {
            var value = userDataSnapshot.data;
            UserManager.userdata = value.data();  //static class where userData is stored
            return null;
          } else {
            return Scaffold(
              body: Center(
                child: CircularProgressIndicator(),
              ),
            );
          }
        });
  }


  Future<bool> checkIfUserExists(String uid) async {
    try {
      var collectionRef = FirebaseFirestore.instance.collection('users');
      var doc = await collectionRef.doc(uid).get();
      return doc.exists;
    } catch (e) {
      throw e;
    }
  }

EDIT This is the Button where the user can sign In

ElevatedButton.icon(
            onPressed: () {
              final provider = Provider.of<GoogleSignInProvider>(context, listen: false);
              provider.googleLogin();
            },
            icon: Icon(MdiIcons.google),
            label: Text(
              'Sign In with Google',
              style: TextStyle(fontSize: 16),
            ),
          ),

Solution

  • The problem here is that FutureBuilder is a widget, it should not be used in a function to wait for a future to complete, but in another widget to have callbacks on the completion and change display based on that.

    If not rendered, FutureBuilder will do nothing but be instantiated and occupy memory.

    You should probably modify your code as such:

    ...
    /// Save userData from firestore in a Helper class which is shown on the homescreen
    
    var userData = await FirebaseFirestore.instance.collection('users').doc(myUser.uid).get();
    UserManager.userdata = userData.data();
    ...
    

    Should you want to add a CircularProgress on your main screen, this would be done by lisntening to your Provider in some way.