Steinhammer71
Steinhammer71

Reputation: 125

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

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.

Upvotes: 0

Views: 379

Answers (3)

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();
    }
  },
),

Upvotes: 2

Alex Verbitski
Alex Verbitski

Reputation: 599

Try to change your _checkRole() method the next way:

Future<bool> _checkRole() async {
  var firebaseUser = FirebaseAuth.instance.currentUser;
  return await FirebaseFirestore.instance
    .collection("users")
    .doc(firebaseUser.uid)
    .get()
    // we create the new Future with bool value, depending on 
    // the Firebase response and throw it away as a result of
    // the _checkRole method
    .then((value) => Future.value(value.data()['role']) == 'admin'));
}

And then use a FutureBuilder inside your component. So your layout should look like:

child: FutureBuilder<bool>(
  future: _checkRole,
  builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
    if (snapshot.hasData) { // check whether we have any data in our Future object
      final isAdmin = snapshot.data; // bool type
      return isAdmin
          ? buildListTile(
              'Admin Panel', Icons.admin_panel_settings_sharp, () {
              Navigator.of(context).pushNamed(AdminScreen.routeName);
            })
          : buildListTile('User dashboard', Icons.person, () {}),
    }
    
    // if snapshot doesn't have data return a widget with an error message
    return Center(
      child: Text('Error!'),
    );
  },
),

Upvotes: 1

Mostafa Mahmoud
Mostafa Mahmoud

Reputation: 671

put a default value for isAdmin variable

bool isAdmin = false;

or If you have a model for members, I mean you created a class called Member

class Member {
  // create a method for deteriming if the the member is an admin or not
  bool isAdmin() async {
    var firebaseUser = FirebaseAuth.instance.currentUser;
    await FirebaseFirestore.instance
    .collection("users")
    .doc(firebaseUser.uid)
    .get()
    .then((value) {
      return ((value.data()['role']) == 'admin');
    });
  }
}

Now, you can use this method to check if the member is an admin or not

Upvotes: 0

Related Questions