How to reset Flutter AnimatedGrid with new list of items? - flutter

I am trying the widgets.AnimatedGrid.1 mysample of AnimatedGrid class documentation and I am always getting a RangeError (index): Invalid value: Not in inclusive range whenever I replace at runtime the late ListModel<int> _list in _AnimatedGridSampleState with a new shorter list.
Simply replacing the code of _insert handler with:
void _insert() {
setState(() {
_list = ListModel<int>(
listKey: _gridKey,
initialItems: <int>[7, 6, 5],
removedItemBuilder: _buildRemovedItem,
);
});
}
then clicking on + button will throw a RangeError.
Since build() in AnimatedGridSampleState depends of _list I was expecting that it will build a new AnimatedGrid with the correct initialItemCount and avoiding RangeError:
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: ...,
body: Padding(
padding: const EdgeInsets.all(16.0),
child: AnimatedGrid(
key: _gridKey,
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
),
),
);
}
Yet, the _buildItem(...) it is still being called with the same indexes of the former longer _list. Why?
You can try it by yourself running on the browser in the snippet container of AnimatedGrid page, replacing _insert() code just like shown in the following print screens. You will not see the RangeError but you will see that former items 4, 5, 6 remain on the AnimatedGrid.

To remove items takes Duration(milliseconds: 300). So setState try to rebuild the items meanwhile and cause the issue. In order to overcome this issue, I came up with removing one by one and then inserting item, created another two method on the ListModel.
class ListModel<E> {
.....
void clear() {
for (int i = _items.length - 1; i >= 0; i--) {
removeAt(i);
}
}
void addAll(List<E> item) {
for (int i = 0; i < item.length; i++) {
insert(i, item[i]);
}
}
Now while you like to reset the item.
void _insert() async {
_list.clear();
/// delay to looks good; kDuration takes to remove item, therefore I am using Future method.
await Future.delayed(const Duration(milliseconds: 300));
setState(() {
_list.addAll(<int>[7, 6, 5]);
});
}

it would be better if you could share the function responsible for removing the item but i can guess what might be the problem if you are getting this error when removing the last item in the list
if you are removing an item then you need to be careful about something
first let's take a look at the removing function
E removeAt(int index) {
final E removedItem = _items.removeAt(index);
if (removedItem != null) {
_animatedGrid!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return removedItemBuilder(removedItem, context, animation);
},
);
}
return removedItem;
as you can see at the start of the function we are using the index to remove the item we want to remove and storing it in a new variable
then we are using it here
_animatedGrid!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return removedItemBuilder(removedItem, context, animation);
},);
as you can see we are using the item that we removed from the list because it will be displayed during the animation that's why we need the item but not the index
and we can't use the index directly in this part cause we already removed the item from the list so if we used it like that
_animatedGrid!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return removedItemBuilder(_items[index], context, animation);
},
);
you will be getting a RangeError (index): Invalid value: Not in inclusive range
because this item is already removed and so it's index is out of range

Related

flutter Call sequence of onTab function on ListView

I am trying with https://github.com/flutter/codelabs/blob/master/startup_namer/step6_add_interactivity/lib/main.dart everything works fine but
when i keep debugging point in the onTab function( At line number 61) and breakpoint in ListView.Builder( At line number 38 ).
OnTab method is getting called first after that only ListView is getting called but i'm not able to understand how the index are correctly calculated in onTap method because th actual logic for index is placed at ListView.
ListView
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return const Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
final alreadySaved = _saved.contains(_suggestions[index]);
OnTap
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(_suggestions[index]);
} else {
_saved.add(_suggestions[index]);
}
});
Please explain how the index is getting calculated onTap.
The favorite item is storing Set.
final _saved = <WordPair>{};
Once you click on favorite button, it checks whether it is already on _saved
final alreadySaved = _saved.contains(_suggestions[index]);
Now if alreadySaved is true, it remove from the current tap item from the set.
if (alreadySaved) {
_saved.remove(_suggestions[index]);
}
If it alreadySaved is false mean , _saved doesnt contain the item, it add it.
else {
_saved.add(_suggestions[index]);
}
It is not storing the index, it is storing items value
And _suggestions is holding all generated item.
variable "index" is under the scope of Item builder and ListView and its onTap function can access the variable from its stack memory.
https://www.geeksforgeeks.org/stack-vs-heap-memory-allocation/

The getter 'length' was called on null. Receiver: null. How to solve this error for the list of lists?

i have got list of lists and I need to retrieve data from them. I am using Provider for fetching data from the API. So I screated the ExpansionTile with 2 Listview.builder, because I have read that for retrieving data I need to use some loop for each list, e.g Listview.builder. But now it gives me the error
"The getter 'length' was called on null.
Receiver: null
Tried calling: length"
But when I use print the array isn't null, so I don't get it why I getting this error.
My code is:
class _StopScreensState extends State<StopScreens> {
List<Stop> stop;
List<Routes> routes;
List <Arrival> arrivals;
#override
void didChangeDependencies() {
stop = Provider.of<List<Stop>>(context).toList();
routes = Provider.of<List<Routes>>(context).toList();
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
//The list of lists
Iterable<Common> merge(List<Arrival> arrivals, List<Stop> stop,
List<Routes> route) sync* {
for (int i = 0; i < arrivals.length; i++) {
var fishingTackle = routes.where((v) => v.mrId == arrivals[i].mrId).toList();
var fishBait = stop.where((v) => v.stId == arrivals[i].stId).toList();
yield Common(
id: arrivals[i].mrId,
typeId: arrivals[i].mrId,
fishingTackle: fishingTackle,
fishBait: fishBait,
time: arrivals[i].taArrivetime,
);
}
}
//the common list for all arrays
List<Common> common = merge(arrivals, stop, routes).toList();
return Scaffold(
appBar: AppBar(
title: Text('Остановки'),
),
body: Provider.value(value: common) == null
? Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: common.length,
itemBuilder: (context, index) {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ExpansionTile(title: Text(common[index].fishingTackle[index].mrTitle),
children: [
ListView.builder(itemCount: stop.length,itemBuilder: (context, index){
return ListTile(
title: Text(common[index].fishBait[index].stTitle),
leading: Text(common[index].time.toString()),
);
It's because you have a null list.
Try to always initialized your list with an empty list, so you don't need to handle the null value for each list.
Change:
List<Stop> stop;
List<Routes> routes;
List <Arrival> arrivals;
to
List<Stop> stop = [];
List<Routes> routes = [];
List <Arrival> arrivals = [];
It seems you're not assigning anything to arrivals

How to fix the getter length was called on null in Flutter

I'm getting the NoSuchMethodError: The gettter 'length' was called on null so just wondering how to fix this issue.
The issue happend when I try to get the length of the favorite value.
Favorite View Model
class FavoriteViewModel extends BaseViewModel {
List<FavoriteModel> favorites = [];
void initialize(FavoriteService favProvider) {
favorites = favProvider.getFavorites();
}
}
Reorder Screen
class _ReorderPageState extends State<ReorderPage> {
#override
Widget build(BuildContext context) {
var favProvider = Provider.of<FavoriteService>(context, listen: true);
return BaseView<FavoriteViewModel>(onModelReady: (model) {
model.initialize(favProvider);
}, builder: (context, model, child) {
return model.state == ViewState.Busy
......
Widget reorderWidget(FavoriteViewModel model, BuildContext bcontext) {
return Theme(
data: ThemeData(primaryColor: Colors.transparent),
child: ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
_onParentReorder(oldIndex, newIndex, model);
},
scrollDirection: Axis.vertical,
children: List.generate(
model.favorites.length, // I think the issue is in this line
(index) {
FavoriteModel favorite = model.favorites[index]; // I think the issue is in this line
Did you already try to use elvis operator (similar to typescript and kotlin) ?
model?.favorites?.length
and also, its possible in your viewModel initializer favProvider.getFavorites() is always null ??

Flutter: How to delete item from listview?

DBHelper dbHelper = DBHelper();
List<Map<String, dynamic>> lists;
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Map<String, dynamic>>>(
future: dbHelper.selectMemo(userkey, 1),
builder: (context, snapshot){
if(snapshot.hasData){
if(snapshot.data.length != 0){
lists = List<Map<String, dynamic>>.from(snapshot.data);
return ListView.separated(
separatorBuilder: (context, index){
return Divider(
thickness: 0,
);
},
itemCount: lists.length,
itemBuilder: (context, index){
return ListTile(
title: Text(lists[index]["memo"]),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: (){
setState(() {
lists = List.from(lists)..removeAt(index);
});
},
),
);
},
);
}
}
},
);
}
This is my code. My lists come from sqlflite. And I want to delete my item from Listview. But this code doesn't work. I don't know where I made the mistake.
This behavior is normal. If you print some logs in the build statement, you will find that every time you click the delete button (setState), Widget will build again.
In addition, lists are re-assigned to DB data after each build
lists = List<Map<String, dynamic>>.from(snapshot.data);
So, it looks like the delete operation is not working.
This phenomenon if you've seen Flutter setState part of the source code will be well understood.
In setState, the callback is performed first, and then mark dirty
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
So, there are two ways to solve this problem:
(1) Do not directly change the value of lists, but when the delete button is pressed, to delete the data in the database, so that when Widget build again, the data taken out of the database is correct.
(2) Add a flag to judge whether the data is initialized, and then add a judgment before assigning lists. If the data is initialized, assignment operation will not be carried out
I hope it worked for you. ^-^

BlocBuilder partially update ListView

Project
Hi, I'm trying to use a bloc pattern to create a list view that can be filtered by a TextField
Here is my code
bloc:
class HomeBloc extends Bloc<HomeEvent, HomeState> {
List<Book> displayList = [];
....
#override
HomeState get initialState => UnfilteredState();
#override
Stream<HomeState> mapEventToState(
HomeEvent event,
) async* {
....
//handle filter by input
if(event is FilterListByTextEvent) {
displayList = displayList.where((book){
return book.title.toLowerCase().contains(event.filterString.toLowerCase());
}).toList();
yield FilteredState();
}
}
}
view
class BookList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<HomeBloc, HomeState>(
builder: (context, state) {
final HomeBloc bloc = Provider.of<HomeBloc>(context);
print(bloc.displayList);
return ListView.builder(
shrinkWrap: true,
itemCount: bloc.displayList.length,
itemBuilder: (context, index) {
return Dismissible(
key: UniqueKey(),
background: Container(
color: selectedColor,
),
child: Container(
height: 120,
padding: const EdgeInsets.fromLTRB(20, 4, 20, 4),
child: BookCard(
book: bloc.displayList[index],
),
),
onDismissed: (DismissDirection direction) {
},
);
},
);
},
);
}
}
Problem
I've read some other discussion about bloc pattern and List view but the issue I'm facing here is different.
Every time my Filter event is called, bloc correctly generate a new displayList but, when BlocBuilder rebuild my UI, listview is not correctly updated.
The new filtered list is rendered but old results do not disappear. It seems like the new list is simply stacked on top of the old one.
In order to understand what was happening I tried printing the list that has to be rendered, inside the BlocBuilder before the build method is called.
The printed result was correct. In the console I see only the new filtered elements while in the UI I see both the new one and the old one, one below the other.
What am I missing here?
Thanks
Keep an intermediate event, eg. a ListInit for which you will display a CircularProgressIndicator. BlocBuilder will be stuck on previous state unless you update it over again.
So in your bloc, first yield the ListInit state and then perform filtering operations, and then yield your FilteredState.
Make new state for loading and yield before displayList.
if(event is FilterListByTextEvent) {
yield LoadFilterList();
displayList = displayList.where((book)
{
return
book.title.toLowerCase().contains(event.filterString.toLowerCase());
}).toList();
yield FilteredState();
}