Flutter: How to delete item from listview? - flutter

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. ^-^

Related

How to reset Flutter AnimatedGrid with new list of items?

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

Riverpod showing snackbar on Error and also last known list using statenotifier

I am using riverpod ^1.0.0. I have created a StateClass which extends Equatable. In my StateNotifier i set state depending on events and outcomes. One being an async http request which upon success sets
state=SalesOrderListSuccess(salesOrderListItems: _items);
Upon http client failure however i set state to:
state = SalesOrderListError(error: response.data);
This works, upon success it renders the list in below UI builder. And it also using ref.listen and shows the snackbar. However, because the state changes from SalesOrderListSuccess and i am using ref.watch it seems that it cant keep the former known list and UI. How can i show the snackbar above the last known SalesOrderListSuccess/UI without rendering an entire new Error Page that is empty of all the items i have already managed to render in the list ?
Basically i dont want the list to change, just show a snackbar above last known list before the http client error happend.
Here the current widget: (this requires the SalesOrderListSuccess state in order to show the list).
#override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
final selectedtrack = ref.read(selectedProductIdProvider2.notifier);
ref.listen(todoListProvider, (previous, count) {
print(previous);
print(count);
switch (count.runtimeType) {
case SalesOrderListError:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ohh no some error happend')),
);
}
});
return ListView.builder(
shrinkWrap: true, // 1st add
physics: ClampingScrollPhysics(),
itemCount: (todos as SalesOrderListSuccess).salesOrderListItems.length,
itemBuilder: (context, index) {
final current=
(todos as SalesOrderListSuccess).salesOrderListItems[index];
return ListTile(
title: Text('${current.title}'),
onTap: () {
selectedtrack.state = index;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(),
),
);
});
},
);
}
Hi have a look at riverpod_messages.
I had your same problems and I have written a package for this
https://pub.dev/packages/riverpod_messages/versions/1.0.0
Let me know!

How to reload a FutureBuilder in Flutter?

I have a FutureBuilder here :
FutureBuilder<List<Task>>(
future:
getTasks(),
builder: (BuildContext context, AsyncSnapshot<List<Task>> snapshot){
if(snapshot.hasError){
return const Text('Erreur');
}
if(snapshot.hasData){
return ListView(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: taskList.map((e) => TaskContainer(
task: e
)).toList(),
);
}
return const Center(child: CircularProgressIndicator());
}
)
Which return a list of tasks as colored block container in a calendar as shown below :
The problem is whenever I navigate to another month in the calendar, the FutureBuilder is fetching the again fetching the data from the webservice, which duplicate the tasks every time I change months :
Here is the code of my functions to navigate between months :
_toPreviousMonth(){
setState(() {
datess.clear();
dayss.clear();
startMonth = new DateTime(startMonth.year, startMonth.month - 1, 1);
});
}
_toNextMonth(){
setState(() {
datess.clear();
dayss.clear();
startMonth = new DateTime(startMonth.year, startMonth.month + 1, 1);
});
}
Actually I don't mind the FutureBuilder fetching the data when I change month, because the final goal would be to display tasks from the selected day.
But what I would like is to have a way to suppress the containers build from the previous fetch as I navigate between months,
Thanks for helping :)
Your code is not showing how you are using snapshot in your FututrBuilder??
I suspect you are adding the tasks directly to a list everytime you re-build using setState( ).
Solution: reset your tasks list at the beginning of the buid( ) method
#override Widget build(BuildContext context) {
yourTaskList = [] //if an array or adjust if other type

Streambuilder with ListView showing same data each tile?

Trying to use StreamBuilder to build out a ListView, but it is showing one item in each row of the ListView. I am using StreamBuilder broadcast. I use add(data).
child: StreamBuilder<GoogleAddress>(
stream: pickupStreamController.stream,
builder: (context, snapshot) => ListView.builder(
itemCount: 8,
itemBuilder: (context, index) {
print('Render $index');
if (snapshot.data != null) {
print("${snapshot.data.address}");
return _createListItem(
icon: Icons.location_on,
googleplace: snapshot.data,
onTap: () {
print("${snapshot.data.address}");
});
} else {}
},
),
),
Here is the StreamController code where I add to the stream. It is adding different GoogleAddress data on each iteration.
GoogleAddress addy = GoogleAddress.fromJsonMap(map);
if (sc != null) {
sc.add(addy);
}
StreamBuilder will use the latest value from the stream. To pass a list to StreamBuilder you would need to pass a list to add.
Which means to grow a list over time, you have to keep a regular list and pass it to add after modifying it.
final addresses = [];
GoogleAddress addy = GoogleAddress.fromJsonMap(map);
addresses.add(addy);
controller.add(addresses);

StreamBuilder not updating ListView.builder after data changed in Flutter

I am new to Flutter and facing an issue with StreamBuilder & ListView.builder.
I am making a network call on click of a button(Apply Button) available in the list of the card, based on that other buttons are displayed.
the issue I am facing is that widgets are not updated after successful network call but, when I refresh the page I am getting updated result.
What I am doing wrong?
I am not Using any State into this. Do I need to use it?
Widget Code
StreamBuilder(
initialData: [],
stream: dataBoc.ListData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemBuilder: (BuildContext context, index) {
return InkWell(
key: Key(snapshot.data[index]["lid"]),
child: DataCard(
DataModel(snapshot.data[index]),
),
onTap: () {
Navigator.pushNamed(context, "/detailPage",
arguments: snapshot.data[index]["id"]);
},
);
},
itemCount: snapshot.data.length,
);
}
},
),
Bloc Code
//Here is how I am adding data to the stream
if (res.statusCode == 200) {
var data = json.decode(res.body);
if (data['status'] == true) {
// listDataStream.sink.add(data['result']);
listDataStream.add(data['result']);
} else {
listDataStream.sink.addError("Failed to Load");
}
}
Expected result: When I make Network call and if it succeeds then based on network result other buttons must be displayed on the appropriate card.
I have fixed this issue. The issue was my widget tree was not well structured and it was breaking the Widget build process.