How to solve: Flutter - How can I retrieve a bool from an external function?

Question

Asked by Steinhammer71 on December 08, 2021 (source).

I'm trying to find whether the logged in user is an admin or a normal user. I have already created a global function (then called it in initState) to check whether the role is admin or not by setting a bool to true or false as follows:

bool isAdmin;
  _checkRole() async {
    var firebaseUser = FirebaseAuth.instance.currentUser;
    await FirebaseFirestore.instance
        .collection("users")
        .doc(firebaseUser.uid)
        .get()
        .then((value) {
      if ((value.data()['role']) == 'admin') {
        isAdmin = true;
      } else {
        isAdmin = false;
      }
      return isAdmin;
    });
  }

And here in the drawer I did the following:

          isAdmin
              ? buildListTile(
                  'Admin Panel', Icons.admin_panel_settings_sharp, () {
                  Navigator.of(context).pushNamed(AdminScreen.routeName);
                })
              : buildListTile('User dashboard', Icons.person, () {}),

But when I open the drawer, I receive Failed assertion: boolean expression must not be null Any idea on how to fix this issue?

and thank you.

Answer

Question answered by developer e (source).

Short answer:
isAdmin isn't initialized when you are building your ListTile because the async function hasn't had a chance to finish running.

Longer answer:
Your build method happens synchronously with the rest of the code, meaning it happens one line after another. Your _checkRole() method happens asynchronously, meaning it will get around to it whenever it gets around to it. So when you try initializing isAdmin in your initState method it is running a network call (which takes a long time in terms of program time) and waiting till the network call finishes to set isAdmin. Meanwhile, your build is running and trying to build without knowing that it is supposed to wait for isAdmin to be set.

A Solution:
(note, there are many ways to solve this, this is just one)
Use a FutureBuilder or StreamBuilder to load the variable and set the variable type to Future or the equivalent for streams and listen to the state change and build your UI accordingly.

Here's a basic example. Careful copy/pasting. I didn't run the code. This is just the general idea.

Future<bool> isAdmin;

FutureBuilder<String>(
  future: Globals.isAdmin,
  builder: (BuildContext context, AsyncSnapshot<Bool> snapshot) {
    if (snapshot.hasData) { //
      var isAdmin = snapshot.data;
      // use the value for isAdmin
      if (isAdmin == true) {
        return Container();
      } else {
        return Container();
      }
    } else if (snapshot.hasError) {
      //handle your error
      return Container();
    } else {
      // handle your loading
      return CircularProgressIndicator();
    }
  },
),
DART FIREBASE FLUTTER GOOGLE-CLOUD-FIRESTORE
SHARE: