1
votes

I have a flutter app where a list is generated with ListView.Builder, and where the itemCount is the number of documents in a firestore collection.

When I add a document to the collection I can see that the value snapshot.data.documents.length changes by printing it, but the the itemCount does not change, which causes the following error:

Invalid value: Not in range 0..17, inclusive: 18

Here is a GitHub thread I created about the same problem: https://github.com/flutter/flutter/issues/39206

And here is the code for the page in question, and the list i'm getting the error from is the one in the StreamBuilder close to the bottom:

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

import 'package:cloud_firestore/cloud_firestore.dart';

/*
Visar kontaktinformation
 */

class Contact extends StatefulWidget {
  @override
  _ContactState createState() => _ContactState();
}

class _ContactState extends State<Contact> {
  _hasDesc(desc) {
    if (desc == '') {
      return false;
    } else {
      return true;
    }
  }
  String sortby = 'namn';
  bool decending = false;

  var showInfo;
  TextEditingController controller = new TextEditingController();
  String filter;

  @override
  void initState() {
    super.initState();
    controller.addListener(() {
      setState(() {
        filter = controller.text.toLowerCase(); //Gör om till gemener för att inte vara skiftlägeskänslig
      });
    });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  Widget _personer(context, DocumentSnapshot document, index) {
    //Skapar lista från databasen med kontaktinformation
    //Denna lista måste vara i rätt ordning i databasen
    final info = List<String>.from(document['info']);

    //Om sökrutan är tom visas alla personer, om inte så visas bara de som matchar filtret
    if (filter == null ||
        filter == '' ||
        document['namn'].toLowerCase().contains(filter) ||
        document['beskrivning'].toLowerCase().contains(filter)) {
      return Column(
        children: <Widget>[
          ListTile(
            onTap: () {
              setState(() {
                for (int i = 0; i < showInfo.length; i++) {
                  if (i != index) {
                    showInfo[i] = false; // för att enbart ett kort ska vara expanderat åt gången
                  }
                }
                showInfo[index] = !showInfo[index];
              });
            },
            title: Padding(
              padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
              child: Column(
                children: <Widget>[
                  Text(
                    document['namn'],
                    textAlign: TextAlign.center,
                    style: Theme.of(context).textTheme.headline,
                  ),
                  Visibility(
                    visible: _hasDesc(document['beskrivning']),
                    child: Text(
                      document['beskrivning'],
                      textAlign: TextAlign.center,
                      style: Theme.of(context).textTheme.subtitle.copyWith(fontSize: 20),
                    ),
                  ),
                  Visibility(
                    visible: showInfo[index],
                    child: ListView.builder(
                      //Bygger lista med kontaktinfo för varje person
                      physics: NeverScrollableScrollPhysics(),
                      shrinkWrap: true,
                      itemCount: info.length,
                      itemBuilder: (context, index) {
                        return Padding(
                          padding: const EdgeInsets.only(top: 5),
                          child: ButtonTheme(
                            child: GestureDetector(
                              onTap: () {
                                Clipboard.setData(ClipboardData(text: info[index]));
                                //skapar snackbar
                                final copiedTextSnackBar = SnackBar(
                                  content: Text('"${info[index].replaceAll('/', '')}" har kopierats'),
                                  action: SnackBarAction(
                                    label: 'Okej',
                                    onPressed: () => Scaffold.of(context).hideCurrentSnackBar(),
                                  ),
                                );
                                //Stänger eventuell snackbar och viar en ny
                                Scaffold.of(context).hideCurrentSnackBar();
                                Scaffold.of(context).showSnackBar(copiedTextSnackBar);
                              },
                              child: Text(
                                info[index].replaceAll('/', '\n'),
                                textAlign: TextAlign.center,
                                style: Theme.of(context).textTheme.body1.copyWith(
                                  fontSize: 16,
                                  color: Color(0xff555555),
                                ),
                              ),
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
          Divider(
            color: Colors.black,
          ),
        ],
      );
    } else {
      return SizedBox(
        height: 0, //Visar ingenting om filtret inte stämmer
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Row(
            children: <Widget>[
              Flexible(
                child: TextField(
                  decoration: InputDecoration(
                    contentPadding: EdgeInsets.fromLTRB(20, 10, 20, 10),
                    hintText: 'Sök',
                    border: InputBorder.none,
                  ),
                  controller: controller,
                ),
              ),
              Text('Sortera:   ', style: TextStyle(fontSize: 16, color: Color(0xff555555)),),
              DropdownButton<String>(
                  value: sortby,
                  onChanged: (String newValue) {
                    setState(() {
                      sortby = newValue;
                    });
                  },
                  items: [
                    DropdownMenuItem(
                      value: 'namn',
                      child: Text('Namn'),
                    ),
                    DropdownMenuItem(
                      value: 'beskrivning',
                      child: Text('Titel'),
                    )
                  ]
              ),
              Stack(
                children: <Widget>[
                  Visibility(
                    visible: decending,
                    child: IconButton(
                      icon: Icon(Icons.arrow_upward),
                      onPressed: () => setState(() {
                        decending = false;
                      }),
                    ),
                  ),
                  Visibility(
                    visible: !decending,
                    child: IconButton(
                      icon: Icon(Icons.arrow_downward),
                      onPressed: () => setState(() {
                        decending = true;
                      }),
                    ),
                  )
                ],
              )
            ],
          ),
          Expanded(
            child: Container(
              child: StreamBuilder(
                stream: Firestore.instance.collection('kontakt').orderBy(sortby, descending: decending).snapshots(), //Hämtar data från databas
                builder: (context, snapshot) {
                  //För att inte skriva över existerande lista:
                  if (showInfo == null) {
                    //Listan genereras här för att slippa kalla på databasen två ggr
                    showInfo = List.generate(snapshot.data.documents.length, (index) => false);
                  }
                  if (!snapshot.hasData) {
                    return Container();
                  } else if (snapshot.hasData) {
                    print(snapshot.data.documents.length);
                    return ListView.builder(
                      itemCount: snapshot.data.documents.length,
                      itemBuilder: (context, index) =>
                          _personer(context, snapshot.data.documents[index], index),
                    );
                  } else {
                    return Center(
                      child: Text("Error"),
                    );
                  }
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}
1

1 Answers

2
votes

I believe it is because of this line:

if (showInfo == null) {
  showInfo = List.generate(snapshot.data.documents.length, (index) => false);
}

the showInfo List only gets updated once due to the condition provided. at first, showInfo is null so it gets updated. On consecutive rebuilds, the List doesn't get updated because it is not equal to null anymore. try removing the if condition and see what happens.