3

I'm new to flutter and really need help with my problem regarding reading/calculating the number of children from the firebase real-time database.

  • The database I use has several categories.
  • Each category has several cases.

What I want, is to extract the information from the database, how many cases each category has and to show this information in a list. That means - to show the name of the category AND how many children (cases) this category has (totalCases)...

Here is my code, I'm struggling with:

    import '../components/category_list_tile.dart';
    import 'package:firebase_database/ui/firebase_animated_list.dart';
    import 'package:flutter/material.dart';
    import 'package:modal_progress_hud/modal_progress_hud.dart';
    import '../constants.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'package:firebase_database/firebase_database.dart';
    import 'dart:async';
    
    class ScreenCategoryList extends StatefulWidget {
      static String id = 'screen_category_list';
      final FirebaseApp app;
    
      ScreenCategoryList({this.app});
      @override
      _ScreenCategoryListState createState() => _ScreenCategoryListState();
    }
    
    class _ScreenCategoryListState extends State<ScreenCategoryList> {
      final referenceDatabase = FirebaseDatabase.instance;
      final _dbRef = FirebaseDatabase.instance.reference().child("de");
    
      static int number = 100;
      bool showSpinner = false;
      DatabaseReference _databaseReference;
    
      @override
      void initState() {
        final FirebaseDatabase database = FirebaseDatabase(app: widget.app);
        _databaseReference = database.reference().child("de");
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.white, Colors.white],
            ),
            image: const DecorationImage(
                image: AssetImage("images/background.png"), fit: BoxFit.cover),
          ),
          child: Scaffold(
            backgroundColor: Colors.transparent,
            appBar: AppBar(
              toolbarHeight: 60.0,
              elevation: 0.0,
              backgroundColor: Colors.black12,
              leading: Padding(
                  padding: EdgeInsets.only(left: 12.0, top: 12.0, bottom: 12.0),
                  child: Image(image: AssetImage('images/lexlogo_black.png'))),
              title: Center(
                child: Column(
                  children: [
                    Text(
                      'Kategorien',
                      style: TextStyle(
                          color: kMainDarkColor,
                          fontFamily: 'Roboto',
                          fontSize: 21.0,
                          fontWeight: FontWeight.bold),
                    ),
                  ],
                ),
              ),
              actions: [
                Padding(
                  padding: EdgeInsets.only(right: 8.0),
                  child: IconButton(
                    icon: Icon(Icons.more_vert_rounded),
                    iconSize: 30.0,
                    color: kMainDarkColor,
                    onPressed: () {},
                    //onPressed: onPressMenuButton,
                  ),
                ),
              ],
            ),
            body: ModalProgressHUD(
              inAsyncCall: showSpinner,
              child: FirebaseAnimatedList(
                query: _databaseReference.child('category'),
                itemBuilder: (
                  BuildContext context,
                  DataSnapshot snapshot,
                  Animation<double> animation,
                  int index,
                ) {
                  Future<int> getNumberOfNodes() async {
                    final response = await FirebaseDatabase.instance
                        .reference()
                        .child('de')
                        .child('category')
                        .child('$index')
                        .child('cases')
                        .once();
                    var nodes = [];
                    response.value.forEach((v) => nodes.add(v));
    
                    return nodes.length;
                  }
    
                  var myNumber = getNumberOfNodes();
                  int myInt = 99;
                  myNumber.then((value) {
                    myInt = value;
                  });
                  number = myInt;
    
                  return CategoryListTile(
                    title: snapshot.value['name'].toString(),
                    successfulCases: 1,
                    totalCases: number,
                    onTitleClick: () {},
                    onInfoButtonClick: () {},
                  );
                },
                reverse: false,
                padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
              ),
            ),
          ),
        );
      }
    }

2 Answers2

1

Since you declare Future<int> getNumberOfNodes() async, you need to FutureBuilder to display that value.

Something like this:

  child: FutureBuilder<int>(
    future: FirebaseDatabase.instance
            .reference()
            .child('de')
            .child('category')
            .child('$index')
            .child('cases')
            .once();
        var nodes = [];
        response.value.forEach((v) => nodes.add(v));
        return nodes.length;
      }
    builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
      List<Widget> children;
      if (snapshot.hasData) {
        return Text("Case count: "+snapshot.data);
      } else if (snapshot.hasError) {
        return Text('Error: ${snapshot.error}'),
      } else {
        return CircularProgressIndicator();
      }
    },
  )

I did not compile or run this code, so please treat it as a pseudo-code. If you get any errors while using this, try to fix them by searching for the error message before reporting back.

So the future is the code that determines the value, and then the builder renders the correct UI based on whether the value is available yet. You'll want to replace the Text("Case count: "+snapshot.data) with your own UI, so the CategoryListTile(...).

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Hi Frank, thank you for your answer. As i already said, I'm really new to flutter. I really don't understand, how integrate the future builder into my code... – Sergej Bechtold Apr 05 '21 at 11:07
  • Did you try to include this code? What happened when you did that? Did you also read up on `FutureBuilder` in general? https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html and https://stackoverflow.com/questions/51983011/when-should-i-use-a-futurebuilder – Frank van Puffelen Apr 05 '21 at 14:37
  • Yes, I did. I spent almost whole day trying that, but nothing worked... What I want, is to extract the information from the database, how many cases each category has and to show this information in a list. That means - to show the name of the category AND how many children (cases) this category has (totalCases)... In Java / Android i could just get the number of children by calling dataSnapshot.getChildrenCount(). It doesn't work in flutter at all. Please Help. – Sergej Bechtold Apr 05 '21 at 15:51
0

Thank you @Frank van Puffelen for your suggestion. Finally could read the number of children of at least one category.

The code had to be changed like this:

class _ScreenCategoryListState extends State<ScreenCategoryList> {
  final referenceDatabase = FirebaseDatabase.instance;

  bool showSpinner = false;
  DatabaseReference _databaseReference;

  @override
  void initState() {
    final FirebaseDatabase database = FirebaseDatabase(app: widget.app);
    _databaseReference = database.reference().child("de");
    super.initState();
  }

  Future<Map<int, int>> getNumberOfNodes() async {
    Map<int, int> caseNumbers = new Map<int, int>();
    // read number of category nodes
    final categoriesNumbersResponse = await FirebaseDatabase.instance
        .reference()
        .child('de')
        .child('category')
        // .child('0')
        // .child('cases')
        .once();
    var categoryNodes = [];
    categoriesNumbersResponse.value.forEach((v) => categoryNodes.add(v));
    int numberOfCategories = categoryNodes.length;

    //read number of cases in category
    for (int i = 0; i < numberOfCategories; i++) {
      final caseResponse = await FirebaseDatabase.instance
          .reference()
          .child('de')
          .child('category')
          .child('$i')
          .child('cases')
          .once();
      var caseNodes = [];
      caseResponse.value.forEach((v) => caseNodes.add(v));
      int numberOfCases = caseNodes.length;
      caseNumbers[i] = numberOfCases;
    }
    return caseNumbers;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.white, Colors.white],
        ),
        image: const DecorationImage(
            image: AssetImage("images/background.png"), fit: BoxFit.cover),
      ),
      child: Scaffold(
        backgroundColor: Colors.transparent,
        appBar: AppBar(
          toolbarHeight: 60.0,
          elevation: 0.0,
          backgroundColor: Colors.black12,
          leading: Padding(
              padding: EdgeInsets.only(left: 12.0, top: 12.0, bottom: 12.0),
              child: Image(image: AssetImage('images/lexlogo_black.png'))),
          title: Center(
            child: Column(
              children: [
                Text(
                  'Kategorien',
                  style: TextStyle(
                      color: kMainDarkColor,
                      fontFamily: 'Roboto',
                      fontSize: 21.0,
                      fontWeight: FontWeight.bold),
                ),
              ],
            ),
          ),
          actions: [
            Padding(
              padding: EdgeInsets.only(right: 8.0),
              child: IconButton(
                icon: Icon(Icons.more_vert_rounded),
                iconSize: 30.0,
                color: kMainDarkColor,
                onPressed: () {},
                //onPressed: onPressMenuButton,
              ),
            ),
          ],
        ),
        body: FutureBuilder<Map<int, int>>(
          future: getNumberOfNodes(),
          builder: (BuildContext context,
              AsyncSnapshot<Map<int, int>> casesSnapshot) {
            if (casesSnapshot.hasData) {
              return FirebaseAnimatedList(
                reverse: false,
                padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
                query: _databaseReference.child('category'),
                itemBuilder: (
                  BuildContext context,
                  DataSnapshot categorySnapshot,
                  Animation<double> animation,
                  int index,
                ) {
                  int numberOfCases = casesSnapshot.data[index];
                  //print('number  of cases  $_counter, $numberOfCases');
                  return CategoryListTile(
                    title: categorySnapshot.value['name'].toString(),
                    successfulCases: 10,
                    totalCases: numberOfCases,
                    onTitleClick: () {},
                    onInfoButtonClick: () {},
                  );
                },
              );
            } else if (casesSnapshot.hasError) {
              return Center(
                child: Column(
                  children: <Widget>[
                    Icon(
                      Icons.error_outline,
                      color: Colors.red,
                      size: 60,
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 16),
                      child: Text('Error: ${casesSnapshot.error}'),
                    )
                  ],
                ),
              );
            } else {
              return Center(
                child: Column(
                  children: <Widget>[
                    SizedBox(
                      child: CircularProgressIndicator(),
                      width: 60,
                      height: 60,
                    ),
                    Padding(
                      padding: EdgeInsets.only(top: 16),
                      child: Text('Awaiting result...'),
                    )
                  ],
                ),
              );
            }
          },
        ),
      ),
    );
  }
}