Search code examples
firebasefluttergoogle-cloud-firestorereturn-value

InitState in flutter, deriving document value from firestore


I am working on an app where I need to filter out every user that signs in. Once they signed in, they will be redirected to wrapper that checks if the user ID exists on Firestore document collection. Here is my code.

class adminCheck extends StatefulWidget {
  const adminCheck({Key? key}) : super(key: key);

  @override
  State<adminCheck> createState() => _adminCheckState();
}

class _adminCheckState extends State<adminCheck> {

  User? user= FirebaseAuth.instance.currentUser;
  bool isAdmin=false;


  void initState() {
  checkIfAdmin();
  super.initState();
  }

  @override
  Widget build(BuildContext context) {


    if (isAdmin==true){
      countDocuments();
      return HomeScreen();
    }else{
        return notAdmin();
    }
  }


  void checkIfAdmin() async{
    DocumentSnapshot currentUser= await FirebaseFirestore.instance.collection('Administrator')
        .doc(user!.email)
        .get();
    if(currentUser.exists){
      print("This user is admin");
      setState((){
        isAdmin=true;
      });
    }
    if(!currentUser.exists){
      print("This user is not an admin");
    }
  }
 
}

The problem is it returns the notAdmin() class even the void method returns true which supposed to return HomeScreen(), and after few seconds, it will return HomeScreen(). I think there is a delay happening from initializing the data coming from the Firestore. Please help me with my problem. Or is there a way so that it will wait to be initialized first before returning any of those two classes?


Solution

  • The purpose of initState is to determine the state of the widget when it first renders. This means that you can't use asynchronous information (such as data that you still need to load from Firestore) in initState.

    If the widget should only be rendered once the data is available, you should not render it until that data has been loaded. You can do this by adding another value to the state to indicate while the document is loading:

    class _adminCheckState extends State<adminCheck> {
    
      User? user= FirebaseAuth.instance.currentUser;
      bool isAdmin=false;
      bool isLoading=true; // 👈
    

    And then use that in rendering:

    @override
    Widget build(BuildContext context) {
      if (isLoading) {                      // 👈
        return CircularProgressIndicator(); // 👈
      } else if (isAdmin==true){
        countDocuments();
        return HomeScreen();
      }else{
          return notAdmin();
      }
    }
    

    You can render whatever you want while the data is loading of course (or nothing at all), but the CircularProgressIndicator is typically a reasonable default.

    And finally of course, you'll want to clear the loading indicator when the isAdmin is set:

    void checkIfAdmin() async{
      DocumentSnapshot currentUser= await FirebaseFirestore.instance.collection('Administrator')
          .doc(user!.email)
          .get();
      if(currentUser.exists){
        setState((){
          isAdmin=true;
          isLoading=false; // 👈
        });
      }
      if(!currentUser.exists){
        print("This user is not an admin");
      }
    }
    

    This pattern is so common that it is also encapsulated in a pre-built widget. If that sounds appealing, you'll want to look at using a FutureBuilder.