I am looking for a way to remove a ListView item from outside of ListView.builder. Item name and index are known. I was hopping to achieve this via ListView.removeAt(itemIndex); but unfortunately this does not work.
In general... there are two FloatingActionButtons: fab1 and fab2. Once fab2 is pressed removeItem(itemName) function is called in order to remove data from database (this works OK) and once data is removed item should be removed from ListView (this does not work since I do not know how to refer to ListView).
Could you please advise on how to remove an item from a ListView in this case?
The full code is attached bellow:
Future<List<PostListDetails>> postsFuture = getPosts();
bool showButton = false;
String itemName ="";
int itemIndex = 0;
static Future<List<PostListDetails>> getPosts() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final token = await prefs.getString('token');
final listID = await prefs.getString('listID');
final lista = await prefs.getString('lista');
Response response = await post(Uri.parse('someURL'),
headers: <String, String>{
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
body: <String, String>{
'listID': '$listID',
},
);
if(response.statusCode != 200){
print('Błąd połączenia z bazą danych... status[${response.statusCode}]');
}
final List body = json.decode(response.body);
return body.map((e) => PostListDetails.fromJson(e)).toList();
}
void removeItem(String nazwa) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final token = await prefs.getString('token');
final listID = await prefs.getString('listID');
try{
Response response = await post(Uri.parse('SomeURL'),
headers: <String, String>{
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
body: <String, String>{
'listID': '$listID',
'nazwa': nazwa,
},
);
if(response.statusCode == 200) {
//ListView.removeAt(itemIndex); <--- I do not know how to do this
}else{
print('Błąd połączenia z bazą danych... status[${response.statusCode}]');
}
}catch(e){
print(e.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton.extended(
heroTag: "fab1",
onPressed: () {
print('---> addList()');
// addList();
},
icon: const Icon(Icons.add_circle_outline),
label: const Text('Dodaj'),
),
if (showButton)
FloatingActionButton.extended(
heroTag: "fab2",
onPressed: () {
print('---> removing $itemName');
removeItem(itemName); // this function removes data from database
setState(() => showButton = false);
},
icon: const Icon(Icons.delete),
label: const Text('Usuń'),
),
],
),
appBar: AppBar(
backgroundColor: Colors.grey[900],
title: const Text('Lista zakupowa'),
actions: [
IconButton(
onPressed: signUserOut,
icon: const Icon(Icons.logout),
),
],
),
body: Center(
child: FutureBuilder<List<PostListDetails>>(
future: postsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasData) {
return buildPosts(snapshot.data!);
} else {
return const Text("Brak danych...");
}
},
),
),
);
}
@override
Widget buildPosts(List<PostListDetails> posts) {
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
int customCompare(PostListDetails a, PostListDetails b) {
if (a.status != b.status) {
return a.status!.toInt() - b.status!.toInt();
}
final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
if (categoryCompare != 0) {
return categoryCompare;
}
return a.nazwa.toString().compareTo(b.nazwa.toString());
}
return GestureDetector(
onLongPress: () {
itemName = post.nazwa!;
itemIndex = index;
setState(() => showButton = true);
},
child: Container(
color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
height: 50,
width: double.maxFinite,
child: Row(
children: [
Expanded(
flex: 8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post.nazwa!,
textAlign: TextAlign.left,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15, color: Colors.black),
),
Text(
'${post.waga!.toString()} g.',
textAlign: TextAlign.left,
style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: Colors.grey),
),
],
),
),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Expanded(
flex: 0,
child: Row(),
),
Expanded(
flex: 10,
child: Row(
children: [
CatIcon(imagePath: (Cat2Ico(post.kategoria!.toString()))),
Checkbox(
value: Int2Bool(post.status!.toInt()),
onChanged: (value) {
setState(() {
post.status = Bool2Int(value!);
});
saveStatus(post.nazwa!, post.status!, index);
posts.sort(customCompare);
},
),
],
),
),
],
),
),
],
),
),
);
},
);
}
UPDATE 1 I have updated the source code and moved ListView.remove... into removeItem function.
UPDATE 2
Bellow final code after the changes:
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton.extended(
heroTag: "fab1",
onPressed: () {
print('---> addList()');
// addList();
},
icon: const Icon(Icons.add_circle_outline),
label: const Text('Dodaj'),
),
if (showButton)
FloatingActionButton.extended(
heroTag: "fab2",
onPressed: () {
removeItem(itemName).then((value){
setState((){
itemToRemove = true;
});
});
setState(() => showButton = false);
},
icon: const Icon(Icons.delete),
label: const Text('Usuń'),
),
],
),
appBar: AppBar(
backgroundColor: Colors.grey[900],
title: const Text('Lista zakupowa'),
actions: [
IconButton(
onPressed: signUserOut,
icon: const Icon(Icons.logout),
),
],
),
body: Center(
child: FutureBuilder<List<PostListDetails>>(
future: postsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasData) {
return buildPosts(snapshot.data!);
} else {
return const Text("Brak danych...");
}
},
),
),
);
}
@override
Widget buildPosts(List<PostListDetails> posts) {
if (itemToRemove) {
posts.removeWhere((element) => element.nazwa == itemName);
itemToRemove=false;
}
return ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
int customCompare(PostListDetails a, PostListDetails b) {
if (a.status != b.status) {
return a.status!.toInt() - b.status!.toInt();
}
final int categoryCompare = a.kategoria.toString().compareTo(b.kategoria.toString());
if (categoryCompare != 0) {
return categoryCompare;
}
return a.nazwa.toString().compareTo(b.nazwa.toString());
}
return GestureDetector(
onLongPress: () {
itemName = post.nazwa!;
itemIndex = index;
setState(() => showButton = true);
},
child: Container(
color: post.status! % 2 == 1 ? Colors.grey[100] : Colors.white,
margin: const EdgeInsets.symmetric(vertical: 0.5, horizontal: 0),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
height: 50,
width: double.maxFinite,
child: Row(
children: [
Expanded(
flex: 8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post.nazwa!,
textAlign: TextAlign.left,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15, color: Colors.black),
),
Text(
'${post.waga!.toString()} g.',
textAlign: TextAlign.left,
style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: Colors.grey),
),
],
),
),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Expanded(
flex: 0,
child: Row(),
),
Expanded(
flex: 10,
child: Row(
children: [
CatIcon(imagePath: (Cat2Ico(post.kategoria!.toString()))),
Checkbox(
value: Int2Bool(post.status!.toInt()),
onChanged: (value) {
setState(() {
post.status = Bool2Int(value!);
});
saveStatus(post.nazwa!, post.status!, index);
posts.sort(customCompare);
},
),
],
),
),
],
),
),
],
),
),
);
},
);
}
This could be achieved in many ways.
Wait until the item is successfully deleted from the database then updated the app. This will use loaders to await the database transaction and setState to update the UI once completed.
By getting the index of the list item and deleting it by updating the state of the app. posts.removeAt(itemIndex);
I would suggest you use way 1. Because you'll be able to tell if something in the DB went wrong simply by using Future
and .then()
Do this
void removeItem(String nazwa) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final token = await prefs.getString('token');
final listID = await prefs.getString('listID');
try{
Response response = await post(Uri.parse('SomeURL'),
headers: <String, String>{
'Accept': 'application/json',
'Authorization': 'Bearer $token',
},
body: <String, String>{
'listID': '$listID',
'nazwa': nazwa,
},
);
if(response.statusCode == 200) {
//ListView.removeAt(itemIndex); Don't need this
// just refresh UI here
setState(() { });//<==remove this if you do not want to refresh the entire screen
}else{
print('Błąd połączenia z bazą danych... status[${response.statusCode}]');
}
}catch(e){
print(e.toString());
}
}
Then for the UI to update based on the change of DB
Center(
child: FutureBuilder<List<PostListDetails>>(
future: postsFuture,//Replace with getPosts() to directly/actively read from DB
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasData) {
return buildPosts(snapshot.data!);
} else {
return const Text("Brak danych...");
}
},
),
For you to show change in progress use CircularProgressIndicator()
like this
...
bool isLoading = false;
...
FloatingActionButton.extended(
heroTag: "fab2",
onPressed: () {
print('---> removing $itemName');
//loading
setState((){
isLoading = true;
});
removeItem(itemName).then((value){
setState((){
posts.removeWhere((element) => element.itemName == itemName);//<==please confirm that element.itemName is in your model class else correct it
isLoading = false;
});}; // this function removes data from database
setState(() => showButton = false);
},
icon: const Icon(Icons.delete),
label: const Text('Usuń'),
),
use this in you body like this
body: isLoading ? Center(child: CircularProgressIndicator()) : Center(
child: FutureBuilder<List<PostListDetails>>(
future: postsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.hasData) {
return buildPosts(snapshot.data!);
} else {
return const Text("Brak danych...");
}
},
),
),