Search code examples
firebasefluttergoogle-cloud-firestorestatetoggleswitch

How to change the state of an particular toggle switch inside the StreamBuilder in flutter


I'm building a school service tracking app using Flutter. Since I'm a newbie to mobile app development I'm facing lots of errors. One of them is,

After an user logged in to the app, in the dashboard it will show their children profiles in a card widget. Each profile as a toggle switch button to update their attendance.

Whenever they tap on the switch button the data is updating in database. But don't know how to change the tapped toggle switch button's state.

I'm using Google Firebase Firestore database.

This is my database structure.

Profiles(Children) table structure.

Profiles(children) table structure

Following is the Attendance table structure. Each document will create daily under the name of particular date. Inside each document profile ids stores as sub collections.

Attendance table structure

Inside each profile sub collection a document will have attendance details under the profile name.

enter image description here

This is how I'm retrieving data from database.

       CustomScrollView(
          slivers: <Widget>[
            SliverList(
              delegate: SliverChildListDelegate([
                whiteLogo(), // this is a logo widget
              ]),
            ),
            StreamBuilder(
              stream: Firestore.instance.collection('profiles').snapshots(),
              builder: (context, snapshot) => SliverList(
                  delegate: SliverChildBuilderDelegate((context, index) =>
                    Container(
                      child: userCard(context, snapshot.data.documents[index]), // profile card
                    ),
                  childCount: snapshot.hasData ? snapshot.data.documents.length : 0,
                ),
              )
            )
          ],
        )

Following is userCard widget

    userCard(BuildContext context, DocumentSnapshot document) {

    var attendanceCollection = Firestore.instance.collection('attendance').document('10-01-2019').collection(document.documentID);

    var documentId = document["name"].toString().toLowerCase();

    var attendanceReference = attendanceCollection.document(documentId);

    bool status = true;


    return Card(
      margin: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 20.0),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.all(Radius.circular(10.0)),
      ),
      elevation: 0.3,
      color: Colors.white,
      child: Padding(
        padding: EdgeInsets.fromLTRB(5.0, 15.0, 5.0, 10.0),
        child: Column(
          children: <Widget>[
            ListTile(
              leading: (
                Container(
                  width: 50.0,
                  height: 50.0,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    image: DecorationImage(
                      fit: BoxFit.cover,
                      image: NetworkImage(
                        '${document['avatar']}'
                      )
                    ),
                  ),
                )
              ),
              title: Text(document['name']),
              trailing: Column(
                children: <Widget>[
                  Text(
                    'Absent/Present',
                    style: TextStyle(
                      color: Color(0xff868686),
                      fontSize: 12.0
                    ),
                  ),
                  Switch(value: status, onChanged: (value) {

                    setState(() {
                      status = value;
                    });

                    //onChange the "attendance_status" value is changing

                    Firestore.instance.runTransaction((transaction) async {
                      DocumentSnapshot freshSnap = await transaction.get(attendanceReference);
                      await transaction.update(freshSnap.reference, {
                        "attendance_status": value
                      });
                    });


                  },)
                ],
              ),
            ),
            horizontalLine(),
            textTile('School', 'Class', 13.0, Color(0xff827f7f)),
            Container(margin: EdgeInsets.only(bottom: 10.0)),
            textTile(document['destination'], document['class'], 16.0, Color(0xff424242)),
            horizontalLine(),
            buttonWrap(context, document)
          ],
        ),
      ),
    );
}

This is the preview of the dashboard.

user dashboard

When I tap on the toggle switch button it update the value in the database. But not the state of the button. Button state is not changing.

I tested by placing the status variable inside the initState, but then the state is affecting all buttons.

I want to change the state of specific toggle switch button when user taps on that. Any help would be much appreciated.

  initState() {
    status = true;
    super.initState();
  }

Solution

  • In your current implementation the switch state is not reflecting the value of the database as there is no subscription for data that is updating its value. You can make the UserCard widget stateless and just read the value of the Switch from attendance collection with a StreamBuilder instead.

    Something like this should do the trick and your attendance switch will set its state depending on the attendance document (and it should be set to false if the document is not yet created)

    Column(
      children: <Widget>[
        Text(
          'Absent/Present',
          style: TextStyle(
            color: Color(0xff868686),
            fontSize: 12.0
          ),
        ),
    
        StreamBuilder(
          stream: attendanceReference.snapshots(),
          initialData: null,
          builder: (ctx, snap) {
            return Switch(
              value: snap.data == null ? false : snap.data["attendance_status"],
              onChanged: (value) {
                Firestore.instance.runTransaction((transaction) async {
                  DocumentSnapshot freshSnap = await transaction.get(attendanceReference);
                  await transaction.update(freshSnap.reference, {
                    "attendance_status": value
                  });
                });
              },
            );
          },
        )