I am trying to create a todo list with AnimatedList. I am able to add new todos but am unable to delete them without having any error. My problem here is that whenever I delete the last todo, this error shows up: Another exception was thrown: RangeError (index): Invalid value: Valid value range is empty: 0.
This is the code for my delete todo
// delete goal
void deleteGoal(int index) {
if (db.dailyList.isEmpty) {
return;
}
_listKey.currentState!.removeItem(index, (context, animation) {
return FadeTransition(
opacity: animation,
child: Tile(
title: db.dailyList[index][0],
goalAchieved: db.dailyList[index][1],
onChanged: (value) =>
{checkBoxChanged(value, index), achievedGoal(index)},
deleteFunction: (context) => deleteGoal(index),
),
);
});
setState(() {
db.dailyList.removeAt(index);
for (int i = index; i < db.dailyList.length; i++) {
db.dailyList[i][2] = i;
}
});
db.updateDatabase();
}
Assume that the list has n items. you are deleting last item, and the index is n-1 (because list indexing starts from zero). When you delete last item, index is still remaining n-1. In the last for loop, you are trying to iterate the list from n-1, but the currect value should be n-2 (because one item is deleted and the list size decreased).
Conclusion: db.dailyList.removeAt(index) >> for (int i = index; ... >> db.dailyList[i] makes error
Related
So I have a ListView with Checkboxes
bool checked = false;
ListView.builder(
itemCount: logs!.length,
itemBuilder: (context, index) {
Log log = logs[index];
return ExpansionTile(
title:
Checkbox(
value: checked,
onChanged: (curValue) {
checked = curValue;
setState(() {});
}),
)
],
)
},
);
The problem is that when I check one Box in the List, all values are changed, because the variable is global & therefore the same boolean is appended to all checkboxes.
When I pack the boolean inside the ListView, I cant click on the Checkbox at all -> because of the setState the value is always reseted & no change appears
So what can I do to make all Checkboxes clickable without influencing the click state of the other ones?
Since you have only 1 variable to check the state of the checkbox, all other checkbox widgets will also depend on this checked variable.
You have to define for every checkbox a own "checked" variable, you could add a checked variable to the Log class and then query and set it each time.
Checkbox(
value: logs[index].checked,
onChanged: (curValue) {
logs[index].checked = curValue;
setState(() {});
},
),
The issue is here using single variable for all items. You can use a List for selected item
List<Log> selectedLogs = [];
Checkbox(
value: selectedLogs.contains(log),
onChanged: (curValue) {
if(selectedLogs.contains(log)){
selectedLogs.remove(log);
}else {
selectedLogs.add(log);
}
setState(() {});
}),
)
I am loading posts from firebase and want to call a function when user scroll to each new post. Basically I want to store user's id in 'Post' collection who view the post. But I am unable to get post ID on scrolling so that I update record on firebase.
There is no such easy way to do this, but you can use VisibilityDetector from visibility_detector package:
You can get the index of the last list item that is completely visible on screen by wrapping each list item with a VisibilityDetector.
_visibleItem = 0;
itemBuilder: (context, index) {
return VisibilityDetector(
key: Key(index.toString()),
onVisibilityChanged: (VisibilityInfo visibilty) {
if (visibilty.visibleFraction == 1)
setState(() {
log(_visibleItem);
_visibleItem = index;
});
},
child: ListTile(title: Text("Index $index"))
);
},
I have been trying to add an button to the end of a listview builder. I tried to do what was suggested in this question: Flutter: How to add a button widget at the end of a ListView.builder that contains other type of widgets?. But if i do that i get: "RangeError (index): Invalid value: Not in inclusive range 0..49: 50
I tried looking at questions that also had this problem but i couldnt find an anwser that fixed it.
List<Anime> animeList = animeData.animeList;
print(animeList.length);
return ListView.builder(
padding: EdgeInsets.only(bottom: 30, top: 10),
itemBuilder: (context, index) {
Anime anime = animeList[index];
if (index == animeList.length) {
return ThinOutlineBtn(
primaryColor: themeWizard.getPrimaryColor(),
darkColor: themeWizard.getBackgroundColor(),
text: "More",
highlightedBorderColor: themeWizard.getPrimaryColor(),
ontap: () {
AnilistApi.followUpQuery(AnilistApi.animeQuery, animeData,
animeData.animePage.currentPage + 1);
},
);
}
return AnimeCard(
anime: anime,
darkMode: themeWizard.getCurrentMode(),
activeIcon: themeWizard.getPrimaryColor(),
background: themeWizard.getCardColor(),
btnHighlightColor: themeWizard.getBackgroundColor(),
textColor: themeWizard.getCardTextColor(),
inActiveIcon: themeWizard.getIconColor(),
);
},
itemCount: animeList.length + 1,
);
Remove the below line and add it after if condition.
Remove this line:
Anime anime = animeList[index];
And the removed line after if condition
if (index == animeList.length) {
....
.....
}
/// Add removed line here
Anime anime = animeList[index];
You are using
itemCount: animeList.length + 1,
You should use
itemCount: animeList.length,
In ListView, indexes starts with ZEROES, not ONE
I am using below code for the listview builder in flutter application I need to get the index of the item in the list upon scrolling. Just Like function of onPageChanged:(){} while using PageView.Builder
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final item = posts[index];
return Post(index: index, title: 'Test', imageUrl: 'https://www.google.com',);
},
);
How can I get that?
There is no such easy way to do this, but you can use VisibilityDetector from visibility_detector package:
You can get the index of the last list item that is completely visible on screen by wrapping each list item with a VisibilityDetector.
itemBuilder: (context, index) {
return VisibilityDetector(
key: Key(index.toString()),
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction == 1)
setState(() {
_currentItem = index;
print(_currentItem);
});
},
child: Post(index: index, title: 'Test', imageUrl: 'https://www.google.com',)
);
},
_currentItem is a variable in the main widget's state that stores the index of the last visible item:
int _currentItem = 0;
Note: This is based on scroll direction, i.e., the last visible item's index equals to the index of the last item that is visible after the scroll; if the scroll is in forward direction, it is the index of the item on bottom of the screen, but if the scroll is in reverse direction, it is the index of the item on top of the screen. This logic can be easily changed though (e.g., using a queue to store the total visible items).
I am have an animated list in my flutter project.
For every element in that list I have a grid of buttons that are placed dynamically from a Firestore stream. Sometimes that will come back with 10 items in the grid but other times that will comeback with 0 items.
When a button on the grid in a list element is pushed it will search firestore and create a new grid of buttons in the next list element below.
The problem that I have is when it comes back with 0 grid buttons I don't want it to create a new list element (an empty list element with no grid buttons). I tried returning a container with 0 size as a work around but animated list still gives it some height so you can see there is a problem. I also understand that this would be bad practice as you have non visible empty list elements in the list.
I start with a list of foods as strings:
List foodListGrids = ['breads','drinks']
I then have an animated list:
AnimatedList(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
key: _FoodandDrinkKey,
initialItemCount: foodListGrids.length,
itemBuilder: (context, index, animation) {
return SizeTransition(
sizeFactor: animation,
child: buildButtonGridItemsMenu(index),
);
},
),
I set the AnimatedList size to the length of the foods list.
I set the child of the Animated List to a class that searches firebase and returns a card with the grid of buttons on it like this:
StreamBuilder(
stream: Firestore.instance
.collection(widget.categoryType)
.where(widget.relationship, isEqualTo: widget.searchString)
.snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Container(width: 0, height: 0,);
} else if (snapshot.hasData) {
List<Widget> widgetList = [];
List<DocumentSnapshot> documentList = snapshot.data.documents;
if (documentList.length > 0) {
for (int i = 0; i < documentList.length; i++) {
widgetList.add(ButtonTheme(
minWidth: 16,
height: 30,
child: GridButton(snapshot, i, widget.listIndex),
));
}
return Container(
width: double.infinity,
child: Wrap(
children: widgetList,
alignment: WrapAlignment.center,
));
} else{
return Text('NO DATA BECAUSE NUMBER OF GRID ITEMS IS 0');
}
} else {
return Text('NO DATA BECAUSE GRID ITEMS CALL IS NULL');
}
},
),
then in the on pressed method for each grid button I add a new list element like this:
void _insertCategoryGridItem(String id, int index) {
if (!foodListGrids.contains(id)) {
foodListGrids.add(id);
_FoodandDrinkKey.currentState.insertItem(index + 1);
}
}
The problem is a chicken or the egg problem I will try to show below:
List item is generated from the index 0 in the food list and all stream data is if from food list index 0 firebase results.
On pressed for a grid item in the first list row is pressed to add a new list row with a new set of grid items. This will then update the food list array and the call for the list to add new row of grid buttons. The issue is because this is in the onpressed for the first rows grid there is no knowledge of what will be returned for the next row so there is no way of knowing if it will return a grid of size 0 in the next list row in the current way it is setup.
I have tried returning null, container of 0 width and height but have had no luck. I am not sure what I can do to fix it.
Thanks for your help
I'm not sure if I get you right but seems that I faced the same problem with AnimatedList and stream of data from the Firestore. The problem is in initialItemCount: property of the AnimatedList.
In my case I wanted to change AnimtedList in two ways:
I wanted to manually add an item and to show it with animation.
I want that if the list is changed due to a new portion of data from the stream - I want the list to be updated without animation of inserting and without errors (out of range).
To solve this I did a dirty hack: when there is an update from the stream I reinit the key of the list, in your case it's _FoodandDrinkKey. So BEFORE you build the AnmatedList just reinit your key _listKeyUserNotes = GlobalKey(); that's how the List will "forget" about it's initialItemCount and will render a new data without any out-of-range errors.
When you want to add a new item manually with animation - use insert().
key: _FoodandDrinkKey,
initialItemCount: foodListGrids.length,
Hope this makes sense.