Home > Back-end >  How to show total item of the listview?
How to show total item of the listview?

Time:06-20

Please help me. I want to show total items of the list view in the card. Basically, first it will show all 3 category. If you click one of the category it will show all the list of item. So, the problem I want to solve is to show the total of item based on the category.

Coding below i tried using .length and List but it does not show the total of item I register.

class CaseListCategory extends StatefulWidget {

  const CaseListCategory ({Key? key}) : super (key : key);

  @override
  _CaseListCategoryState createState() => _CaseListCategoryState();
}

class _CaseListCategoryState extends State<CaseListCategory> {
  @override
  Widget build(BuildContext context) {

    final docCase = FirebaseFirestore.instance.collection('cases').doc();

    List<CriticalCaseList> cases = [];
    
    return Scaffold(
     
      body: Container(
        padding: EdgeInsets.all(10),
        child: ListView(
          children: <Widget>[
            GestureDetector(
            onTap: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => CriticalCaseList(),
                ),
              );
            },
            child: Card(
              elevation: 10,
              color: Colors.red,
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
                child: Container(
                  child: Column(
                    children: [
                      Text(
                        'CRITICAL',
                        style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                      Text(         
                        "${cases.length}",
                        style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                    ],
                  ), 
                ),
              ),
            ),
          ),
          GestureDetector(
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => ModerateCaseList(),
              ),
            );
          },
          child: Card(
            elevation: 10,
            color: Colors.orange,
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
              child: Column(
                children: [
                  Text(
                    "MODERATE",
                    style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                  Text(         
                    "${cases.length}",
                    style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                ],
              ),
            ),
          ),
        ),
        GestureDetector(
          onTap: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => LowCaseList(),
              ),
            );
          },
          child: Card(
            elevation: 10,
            color: Colors.yellow,
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
              child: Column(
                children: [
                  Text(
                    'LOW',
                    style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                  Text(         
                    "${cases.length}",
                    style: TextStyle(letterSpacing: 1.0,fontSize: 20, fontWeight: FontWeight.bold),
                  ),
                ],
              ),
            ),
          ),
        ),
        ],), 
      )
    );
  }
}

Here are the coding of page after i click the card. it show all of item

Category page List of item page

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

  @override
  
  _CriticalCaseListState createState() => _CriticalCaseListState();
}

class _CriticalCaseListState extends State<CriticalCaseList> {
  User? user = FirebaseAuth.instance.currentUser;

  final CollectionReference _cases = FirebaseFirestore.instance.collection('cases');

  @override

  Widget build(BuildContext context) {
      
    return Scaffold(

      appBar: AppBar(title: const Text("Critical Case"),
        backgroundColor: Colors. redAccent,
        centerTitle: true,
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          color: Colors.white,
          iconSize: 30,
          onPressed: () =>  Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (context) => const VolunteerPage())),
        ),
      ),

      // Using StreamBuilder to display all products from Firestore in real-time
      body: StreamBuilder(
        stream: _cases.snapshots(),
        builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
          if (streamSnapshot.hasData) {
            return ListView.builder(
              itemCount: streamSnapshot.data!.docs.length,
              itemBuilder: (context, index) {

                final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index];
                  if(documentSnapshot['priority'] == "Critical" && documentSnapshot['status'] == "Waiting for rescue"){
                  return Card(
                    child: ListTile(
                      title: Text(documentSnapshot['name']),
                      subtitle: Text(documentSnapshot['priority'].toString()),
                      trailing: Icon(Icons.arrow_forward),
                      onTap: () {
                        Navigator.push(context, 
                          MaterialPageRoute(builder: (context) => CaseListView(cid: documentSnapshot['cid']))
                        );
                      },
                    )
                  );
                  
                }
                return Card();
                },
              );
            }
            return const Center(
            child: CircularProgressIndicator(),
          );
        },
      ),
    );
  }
}

CodePudding user response:

Problems

There are two problems with the code.

  1. In CriticalCaseList, documentSnapshot is a snapshot and not the document data itself, so you need to access data from it after calling .data() on the documentSnapshot.

  2. In CaseListCategory, cases.length is not working because there is no part of the code that is filling up cases from Firestore.

  3. It will be improper to use cases.length because cases will contain the total of all the cases (irrespective of their categories). But you want the individual totals of critical, moderate, or low categories.

Solution

So in CriticalCaseList, change the following line

final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index];

to

final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index].data();

(notice .data() appended at the end)

Given that you want to display the total number of cases in each category, what you can do is keep track of each categories total.

So you can have variables for each of them. Then initialize their values in initState. And still in initState, you can use .snapshots().listen() on the collection reference of cases. This way, every time cases are added or removed, The CaseListCategory widget will update the total of each case and display the current total. Also, remember to cancel the StreamSubscription in dispose().

Finally, in the parts of the code where you display the totals, instead of using cases.length, you use the total of the given category. The following should work:

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

import 'critical_case_list.dart';
import 'moderate_case_list.dart';
import 'low_case_list.dart';

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

  @override
  _CaseListCategoryState createState() => _CaseListCategoryState();
}

class _CaseListCategoryState extends State<CaseListCategory> {
  double _criticalCases = 0, _moderateCases = 0, _lowCases = 0;
  late StreamSubscription _listener;

  @override
  void initState() {
    super.initState();
    _listener = FirebaseFirestore.instance
        .collection('cases')
        .snapshots()
        .listen((snap) {
      final cases = snap.docs.map((doc) => doc.data());
      _criticalCases = 0;
      _moderateCases = 0;
      _lowCases = 0;
      for (var caseData in cases) {
        if (caseData['priority'] == 'Critical') _criticalCases  ;
        if (caseData['moderate'] == 'Moderate') _moderateCases  ;
        if (caseData['low'] == 'Low') _lowCases  ;
      }
      setState(() {});
    });
  }

  @override
  void dispose() {
    _listener.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      padding: EdgeInsets.all(10),
      child: ListView(
        children: <Widget>[
          GestureDetector(
            onTap: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => CriticalCaseList(),
                ),
              );
            },
            child: Card(
              elevation: 10,
              color: Colors.red,
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
                child: Container(
                  child: Column(
                    children: [
                      Text(
                        'CRITICAL',
                        style: TextStyle(
                            letterSpacing: 1.0,
                            fontSize: 20,
                            fontWeight: FontWeight.bold),
                      ),
                      Text(
                        '$_criticalCases',
                        style: TextStyle(
                            letterSpacing: 1.0,
                            fontSize: 20,
                            fontWeight: FontWeight.bold),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
          GestureDetector(
            onTap: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => ModerateCaseList(),
                ),
              );
            },
            child: Card(
              elevation: 10,
              color: Colors.orange,
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
                child: Column(
                  children: [
                    Text(
                      "MODERATE",
                      style: TextStyle(
                          letterSpacing: 1.0,
                          fontSize: 20,
                          fontWeight: FontWeight.bold),
                    ),
                    Text(
                      '$_moderateCases',
                      style: TextStyle(
                          letterSpacing: 1.0,
                          fontSize: 20,
                          fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
              ),
            ),
          ),
          GestureDetector(
            onTap: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => LowCaseList(),
                ),
              );
            },
            child: Card(
              elevation: 10,
              color: Colors.yellow,
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(vertical: 30, horizontal: 10),
                child: Column(
                  children: [
                    Text(
                      'LOW',
                      style: TextStyle(
                          letterSpacing: 1.0,
                          fontSize: 20,
                          fontWeight: FontWeight.bold),
                    ),
                    Text(
                      '$_lowCases',
                      style: TextStyle(
                          letterSpacing: 1.0,
                          fontSize: 20,
                          fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    ));
  }
}

Better solution

Your current setup is expensive. Or rather, it will be costly as your app grows or scales. Firebase charges you for every document read. That said, every time CaseListCategory is loaded, Firestore will read all the documents in the cases collection. And every time any document in that collection is created, updated, or deleted, Firestore will fetch all of them again (to update the totals).

A common pattern to reduce such cost is to have a counters collection. Inside it, you will have documents for each category that will maybe have a total property, holding the current total of a given category.

Then you increment or decrement the current count of a given category when a case is created or deleted. A better place to run this logic is in Cloud Functions, where you are sure that the code would run. Updating the counts in clients is not recommended because the client's network might fail or also for security reasons.

So you could have the following code in cloud functions in index.js file.

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();

exports.incrementCaseCount = functions.firestore
  .document('/cases/{caseId}')
  .onCreate(async (snap, _) => {
    const category = snap.data()['priority'].toLowerCase();
    await db
      .doc(`/counters/${category}Cases`)
      .set({ total: admin.firestore.FieldValue.increment(1) }, { merge: true })
      .catch((error) => console.error(error));
  });

exports.decrementCaseCount = functions.firestore
  .document('/cases/{caseId}')
  .onDelete(async (snap, _) => {
    const category = snap.data()['priority'].toLowerCase();
    await db
      .doc(`/counters/${category}Cases`)
      .set({ total: admin.firestore.FieldValue.increment(-1) }, { merge: true })
      .catch((error) => console.error(error));
  });

And then in flutter, in the initState of CaseListCategory, instead of listening to snapshots of the entire cases collection, you can listen to snapshots of only the counters collection. counters collection would have a small number of documents, so it is cheaper to read from them than to read all the documents in the cases collection.

So you can have the following in initState.

    _listener = FirebaseFirestore.instance
        .collection('counters')
        .snapshots()
        .listen((snap) {
      for (var doc in snap.docs) {
        if (doc.id == 'criticalCases') _criticalCases = doc.data()['total'];
        if (doc.id == 'moderateCases') _moderateCases = doc.data()['total'];
        if (doc.id == 'lowCases') _lowCases = doc.data()['total'];
      }
      setState(() {});
    });
  • Related