AnimatedList out of range when StreamBuilder refreshes - flutter

StreamBuilder(
stream: users.where("invites", arrayContains: uid).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
_items = snapshot.data!.docs;
return AnimatedList(
key: listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return _buildItem(index, _items[index], animation);
},
);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Center(
child: SizedBox(
height: 50,
width: 50,
child: CupertinoActivityIndicator()),
),
],
);
}
},
)
I got this StreamBuilder with a stream from FirebaseFirestore. E.g. I have 3 entries in Firebase which match my condition (saved in _items). So far, so good. But if I delete one of those, the stream refreshes but the list throw an error: RangeError (index): Invalid value: Not in inclusive range 0..1: 2
Other way round same problem: Only two are shown and the third is not shown.
Does anyone have an idea why or how to fix this.
Thanks :)

This does not work with Animated List because your index is decreasing. You need to build a new Animated List or I would recommend to use a normal list view.

Related

Flutter ListView with nested ListView throws RangeError for offscreen widgets

First time working in Flutter and not really a software wizard. I've built an app that connects to Firebase and retrieves a data structure as a Map (all after login). This works fine. However, the data is (kind of) a list of lists (or Map of Maps), etc. Basic structure is:
{
title1: {
item-A: {
0: line1,
1: line2,
2: line3,
},
item-B: {
0: line1,
1: line2,
2: line3,
}
},
title2: {
item-A: {
0: line1,
...
}
The schema can have more or fewer titles, items, and lines, but the structure is the same.
The code I've implemented gets the values from the database (Firebase Realtime Database) via FutureBuilder, formats the results, and displays them. Retrieving the results and the general display formatting works as intended, but I can only see one or two of the list widgets before a huge red box is displayed and I get the following error:
The following IndexError was thrown building:
RangeError (index): Index out of range: index should be less than 1: 1
If I run a Hot Refresh or Hot Reload, or just reload the browser, I get a few more rows of Widgets and the error changes to the next index increment:
The following IndexError was thrown building:
RangeError (index): Index out of range: index should be less than 4: 5
If I keep refreshing, eventually the whole list shows up and the error goes away. Obviously this is not the desired behavior, however. I've been working this and testing different approaches but I don't really understand what the issue is.
My code (with a couple of details left out) is as follows:
Widget build(BuildContext context) {
return Consumer<ApplicationState>(
builder: (context, appState, child) {
selectedIndex.add(-1);
return Scaffold(
...
body: FutureBuilder(
future: futureDataRetreiverFunction(),
builder: (BuildContext context, AsyncSnapshot<Map> snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
... // convert the snapshot.data here //
return Container(
...
child: ListView(
shrinkWrap: true,
children: [
Form(
key: _formKey,
child: Column(
children: [
ListView.builder(
scrollDirection: Axis.vertical,
controller: ScrollController(),
shrinkWrap: true,
itemCount: snapshotData.keys.length, // the data is a Map
itemBuilder: (BuildContext context, int index) =>
Column(
children: [
...
Row(
children: [
Text(snapshotData.keys.elementAt(index)),
]
),
Row(
children: [
Text(selectedIndex[index].toString())
]
),
Row(
children: [
Flexible(
fit: FlexFit.loose,
child: Padding(
padding: const EdgeInsets.fromLTRB(5, 0, 0, 10),
child: ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: snapshotData.values.elementAt(index)[1].length,
itemBuilder: (BuildContext context, int index2) =>
Text(snaptshotData.values.elementAt(index)[1][index2])
)
)
)]
)
])
)
])
])
);
...
Vishal pointed out the answer in his first comment to this question. I am using "selectedIndex" as part of a setState({}) call to change some visuals as part of an onTap: () {} call. I was initiating the list with a simple initiation call earlier in the class (List selectedIndex = [];), but then was only adding 1 item to the index in the builder. However, in this case, there are a total of 6 items which get built (more/less in other scenarios), and that was throwing the index error. Which is why it would increment on reload (another seletedIndex.add(-1); was called). I dropped in a quick function to pre-populate the index based on the size of the snapshot data, along the lines of:
for (var counter=0; counter<snapshotData.keys.length; counter++) {
selectedIndex.add(-1);
}
This solved the index RangeError.

how to use two future builder in one page in flutter

In my Flutter app, I need to display two lists that are coming from the database, but I am having trouble getting both lists to display on the same screen. I am using two FutureBuilder widgets, but the first list is displaying correctly while the second list is still loading.
Here is the code I am using:
var future1 = FutureBuilder<List<QuranTextModel>>(
future: database.getQuranText(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ScrollablePositionedList.builder(
itemScrollController: scrollToIndex,
itemCount: snapshot.data!.length,
initialScrollIndex: widget.position,
itemBuilder: (context, index) {
// Build the list item widget here
});
}else{
return const Center(child: CircularProgressIndicator(),);
}
}
);
var future2 = FutureBuilder<List<UrduTextModel>>(
future: database.getUrduTranlation(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ScrollablePositionedList.builder(
itemScrollController: scrollToIndex,
itemCount: snapshot.data!.length,
initialScrollIndex: widget.position,
itemBuilder: (context, index) {
// Build the list item widget here
});
}else{
return const Center(child: CircularProgressIndicator(),);
}
}
);
Column(
children: [
SizedBox(
height: 200,
child: future1,
),
SizedBox(
height: 200,
child: future2,
),
],
)
The first FutureBuilder is used to build a list of QuranTextModel objects, and the second FutureBuilder is used to build a list of UrduTextModel objects. I am using a Column widget to display both lists, with each list contained within a SizedBox widget to give it a fixed height.
The issue I am having is that only the first list is displaying correctly, while the second list is still loading. How can I get both lists to display on the same screen?
Thank you for any help you can provide!
SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 200,
child: future1),
SizedBox(height: 200,child: future2,)
],
),
),
Try this.
also you have to check your future status before populate you can check that by using
if (snap.connectionState == ConnectionState.done) { your code. you can check does snpa has data in it. }
connection state has deferent states that can help you to make your UI more interactive

Flutter Getx multiple lists in body

Got the basic listview working with this code:
body: GetX<LokationListeCtrl>(
builder: (ctrl) => ListView.separated(
separatorBuilder: _separatorBuilder,
itemCount: ctrl.lokationListe.length,
itemBuilder: (BuildContext context, int index) {
return _lokationTile(ctrl.lokationListe.elementAt(index));
},
),
),
Ok great, so now I'll just add another list by wrapping the whole thing in a multiple child widget, but even before doing that, I'm already stuck trying to get the first list working:
body: Column(
children: [
GetX<LokationListeCtrl>(
builder: (ctrl) => ListView.separated(
separatorBuilder: _separatorBuilder,
itemCount: ctrl.lokationListe.length,
itemBuilder: (BuildContext context, int index) {
return _lokationTile(ctrl.lokationListe.elementAt(index));
},
),
),
],
),
Here using a Column, but it applies to all widgets with multiple children. The itemBuilder code is simply newer executed.
It seems like a simple and usefull thing to be able to do - the question is HOW to have multiple list in body, the "GetX way" ?
Found a solution. If we introduce a new Scaffold and wrap it in an Expanded, then GetX stops beeing an insulted little boy, and starts smiling again:
body: Column(
children: [
Expanded(
child: Scaffold(
body: MyBodyPadding(
// add space between appbar and first ListTile
child: GetX<LokationListeCtrl>(
builder: (ctrl) => ListView.separated(
separatorBuilder: _separatorBuilder,
itemCount: ctrl.lokationListe.length,
itemBuilder: (BuildContext context, int index) {
return _lokationTile(
ctrl.lokationListe.elementAt(index));
},
),
),
),
),
),
],
),

Flutter Firebase only let certain data be shown on list view builder

I am trying to only display certain data from a collection. In my example, I only want to show data in the list where the collection in name on firebase is equal to the name of the widget. I am unable to solve this problem
Expanded(
child: Container(
child: FutureBuilder(
future: getPosts(),
builder: (_,snapshot){
if(snapshot.data["name"]== widget.info.name){
return ListView.builder(
itemCount:
x,
itemBuilder: (_,index){
return ListTile(
title: Text(snapshot.data[index].data["name"]),
);
});
Expanded(
child: Container(
child: FutureBuilder(
future: getPosts(),
builder: (_,snapshot){
return ListView.builder(
itemCount:
snapshot.data,
itemBuilder: (_,index){
if(snapshot.data["name"]== widget.info.name){
return ListTile(
title:Text(snapshot.data[index].data["name"]),
);
}else{
return Container();
}
})}))
);
A better option will be to pass the widget name in the getPosts() function you defined and update the firebase query to do the filtering. This way it be much faster as firebase does the require indexing and will do the filtering in O(len(filtered_elements)) rather than current time complexity of O(len(all_elements)).

Combine itemBuilder with StickyHeader in Flutter

I'm new to flutter and I want to organise the elements that I get from Firestore in a list with StickyHeaders. I would like to do it with an itemBuilder and the snapshot I get from the database.
My problem is the itemBuilder builds each item separately and has to be returned, but StickyHeader needs to have all items added as children.
How can I achieve this? Just as a reference I paste my code without the StickyHeader. buildItemBubble returns a Card Widget.
StreamBuilder(
stream: buildSnapshots(_filters),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
return snapshot.data.documents.length == 0
? Center(child: Text('It\s empty here...'))
: CupertinoScrollbar(
child: ListView.builder(
key: new PageStorageKey('randomkey'),
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: snapshot.data.documents.length,
itemBuilder: (ctx, i) =>
buildItemBubble(snapshot, i),
),
);
},
)