flutterfirebasegoogle-cloud-firestoreflutter-streambuilder

Updating Firestore Document Boolean value from ListTile Flutter


I am developing an app that allows users to check kit within bags on vehicles at their base station.

I so far have the app working so that it takes the user information at log in, and shows the bags available at their particular station, in this case 'Test Station'. I can successfully show a listView with the relevant bag information from Firestore 'bags' collection.

What I would like to do, is when a user taps on a listTile, to update the Boolean value for that particular bag (essentially for that particular document within the bags collection) from false to true and back again. I then want the UI to show a green or red circle Icon accordingly.

I can only work out how to do this when I hardcode the docID for the doc I want to update the boolean of, rather than doing it dynamically dependant on which listTile the user taps on. All help to resolve this appreciated!!

so if I call this function when in the onTap:

CollectionReference bags = FirebaseFirestore.instance.collection('bags');

 Future<void> updateBagStatus() {
    return bags
        .doc('test_bag')
        .update({'isChecked': true})
        .then((value) => print("isChecked Updated"))
        .catchError((error) => print('Failed: $error'));
  }

then I can flip the Boolean from false to true for that hardcoded bag.

and this is the rest of the code that shows how I construct the listView builder.

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String thisUserFirstName = '';
  String thisUserLastName = '';
  String thisUserBase = '';
  String thisStation = '';
  String thisBagChecked = '';

  CollectionReference usersCollection =
      FirebaseFirestore.instance.collection('users');
  CollectionReference bags = FirebaseFirestore.instance.collection('bags');

  Future<void> updateBagStatus() {
    return bags
        .doc('test_bag')
        .update({'isChecked': true})
        .then((value) => print("isChecked Updated"))
        .catchError((error) => print('Failed: $error'));
  }

  void _getData() async {
    User user = FirebaseAuth.instance.currentUser!;
    DocumentSnapshot thisUserSnapshot = await FirebaseFirestore.instance
        .collection('users')
        .doc(user.uid)
        .get();

    {
      setState(
        () {
          thisUserFirstName = thisUserSnapshot['first name'];
          thisUserLastName = thisUserSnapshot['last name'];
          thisUserBase = thisUserSnapshot['base station'];
        },
      );
    }
  }

  //   return StreamBuilder(
  //     stream: isCheckedSnapshot,
  //     builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
  //       if (!snapshot.hasData) {
  //         print('There is no data');
  //       }

  @override
  void initState() {
    super.initState();
    _getData();
  }

  // int selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Stream<QuerySnapshot> bags = FirebaseFirestore.instance
        .collection("bags")
        .where("station", isEqualTo: thisUserBase)
        .snapshots();

    return Scaffold(
      backgroundColor: Colors.grey[300],
      appBar: AppBar(
        leading: Icon(Icons.settings),
        title: Text(
          'Welcome $thisUserFirstName',
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
        ),
        actions: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8.0),
            child: GestureDetector(
              onTap: () {
                FirebaseAuth.instance.signOut();
              },
              child: Row(
                children: const [
                  Text('Sign Out'),
                  SizedBox(
                    width: 5,
                  ),
                  Icon(Icons.logout),
                ],
              ),
            ),
          ),
        ],
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          SizedBox(height: 40),
          MyProfileRow(
            rowBanner: 'Signed in as:',
            rowIcon: Icons.person,
            userName: '$thisUserFirstName $thisUserLastName',
          ),
          SizedBox(height: 20),
          MyProfileRow(
            rowBanner: 'Base Station:',
            rowIcon: Icons.house,
            userName: thisUserBase,
          ),
          SizedBox(height: 30),
          Text("All bags at $thisUserBase"),
          SizedBox(
            height: 10,
          ),
          Expanded(
            child: StreamBuilder<QuerySnapshot>(
              stream: bags,
              builder: (
                BuildContext context,
                AsyncSnapshot<QuerySnapshot> snapshot,
              ) {
                if (snapshot.hasError) {
                  return Text('Error!');
                }

                if (snapshot.connectionState == ConnectionState.waiting) {
                  return CircularProgressIndicator();
                }

                final data = snapshot.requireData;

                return ListView.builder(
                  itemCount: data.size,
                  itemBuilder: (context, index) {
                    return Padding(
                      padding: const EdgeInsets.all(5.0),
                      child: Card(
                        elevation: 5,
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: ListTile(
                            title: Text('${data.docs[index]['name']}'),
                            subtitle: Text('${data.docs[index]['isChecked']}'),
                            trailing: Icon(
                              Icons.circle,
                              color: Colors.red,
                            ),
                            onTap: updateBagStatus
                            // onTap: () {
                            //   if (data.docs[index]['isChecked'] == true) {
                            //     print('isChecked = True');
                            //   }
                            //   if (data.docs[index]['isChecked'] == false) {
                            //     print('isChecked = False');
                            //   }
                            // },
                            ),
                      ),
                    );
                  },
                );
              },
            ),
          )
        ],
      ),
    );
  }
}

Click to see how I have Firestone set up:

Image of My Firestore Database:


Solution

  • Your approach is correct. You just need to pass the relevant values as function parameters as shown below:

    Future<void> updateBagStatus(String docId, bool status) {
      return bags
          .doc(docId)
          .update({'isChecked': status})
          .then((value) => print("isChecked Updated"))
          .catchError((error) => print('Failed: $error'));
    }
    
    onTap: () => updateBagStatus(!data.docs[index]['isChecked'], data.docs[index].id)