Flutter Slidable - How to remove the slidable item from the list when i hit the delete button? - flutter

I have a simple list of ListTile s that I nested in Slidable widget using the flutter_slidable package. However there is one issue that when I use the delete button, the item stays on the screen, even though it is successfully removed from the list (If i make a hot reload, it will actually get removed from screen). I tried to add a setState method inside the onPressed function but I cannot add it because the SlidableAction widget is a stateless widget. How can I make the item disappear when I tap on this button?
Here is a small video demonstration. I delete two lists with the delete button. They stay on the screen. I go home screen and come back to see they got deleted.
https://streamable.com/td7blf
Here is my code:
Expanded(
child: ListView.builder(
itemCount: likedNames.length,
itemBuilder: (context, index) {
return Slidable(
key: const ValueKey(0),
endActionPane: ActionPane(
motion: ScrollMotion(),
dismissible: DismissiblePane(onDismissed: () {
likedNames.remove(likedNames[index]);
}),
children: [
SlidableAction(
onPressed: (context) {
likedNames.remove(likedNames[index]); // <- this is the part where I want to do the removing of the item
},
label: 'Delete',
backgroundColor: AppColors.error,
),
],
),
child: ListTile(
onTap: () {},
title: Text(likedNames[index].name),
trailing: Icon(Icons.chevron_right),
),
);
}),
),

onPressed: (context) {
setState() => likedNames.remove(likedNames[index])
},

I've used this solution:
onPressed: (BuildContext context) async {
await Provider.of<MyList>(context, listen: false)
.remove(MyList[index]);
}
MyList is a separate class:
class MyList extends ChangeNotifier
Whit this methods:
final List<String> _myList = [];
List<String> get items => _myList;
.
.
.
Future<bool> remove(String item) async {
_myList.remove(item);
notifyListeners();
return true;
}

Related

How can I do actions for long press & reorder on the same widget?

I am trying to implement a ReorderableListView that can also long-press for a context menu..
I have buildDefaultDragHandles: false, and am using ReorderableDelayedDragStartListener && a GestureDetector - GestureDetector because I need the Offset from the LongPress' details to know where the cursor was, and open the context menu at that location.
With the drag listener as parent of the GestureDetector the LongPress works but the drag doesn't.
With the GestureDetector as parent of the drag listener the drag works but the LongPress doesn't.
I want both to be able to trigger - LongPress to open the context menu, and if the user begins dragging it cancels that to perform the reorder.
In this example the only one that triggers for a LongPress is onLongPressDown, which actually triggers on regular interaction like tap/click rather than actual long presses.
class HomePage extends StatelessWidget {
HomePage({super.key});
final items = ['One', 'Two', 'Three', 'Four', 'Five'];
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: ReorderableListView.builder(
buildDefaultDragHandles: false,
itemBuilder: (context, index) {
return GestureDetector(
key: ValueKey(items[index]),
onLongPress: () => print('onLongPress'),
onLongPressCancel: () => print('onLongPressCancel'),
onLongPressDown: (details) => print('onLongPressDown'),
onLongPressEnd: (details) => print('onLongPressEnd'),
onLongPressMoveUpdate: (details) =>
print('onLongPressMoveUpdate'),
onLongPressStart: (details) => print('onLongPressStart'),
onLongPressUp: () => print('onLongPressUp'),
child: ReorderableDelayedDragStartListener(
index: index,
child: ListTile(
title: Text(items[index].toString()),
),
),
);
},
itemCount: items.length,
onReorder: (oldIndex, newIndex) {},
),
),
);
}
}

How to add a card widget dynamically whenever floating action button is clicked in flutter?

I am very new to flutter and was just curious to know how can we create a new card widget everytime a button (lets say FAB) is clicked.
Suppose this is the card widget :
return Card(
child: Column(
children: [
Text('name'),
Text('standard'),
Text('Roll No'),
],
),
);
I want the cards to build with the same content everytime the FAB is clicked. Can someone help me with this ?
First declare a List of widget type
List<Widget> _cardList = [];
Then create your widget which you want to add on button click, e.g.
Widget _card() {
return Card(
child: Column(
children: [
Text('name'),
Text('standard'),
Text('Roll No'),
],
),
);
}
add your widget in List on button click
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
_cardList.add(_card());
});
},
),
Now use a ListView.builder for create a list of widget
ListView.builder(
shrinkWrap: true,
itemCount: _cardList.length,
itemBuilder: (context, index) {
return _cardList[index];
},
)

Call SnackBar from within an AlertDialog

When a user clicks on delete for an employee, an AlertDialog shall pop up to warn the user.
If the user confirms the deletion, then the AlertDialog disappears and at the bottom of the Scaffold a SnackBar should appear with an Undo function.
Problem:
When I implement the SnackBar method showSnackBar(context, index, employee) within the AlertDialog class I get the following error:
he following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.
showDeleteDialog(BuildContext context, Employee employee, int index) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title:
Text('Are you sure you want to delete: ${employee.name} ?'),
actions: <Widget>[
Row(
children: <Widget>[
FlatButton(
child: Text('Yes'),
onPressed: () {
DatabaseProvider.db.deleteEmployee(employee.id).then(
(_) => BlocProvider.of<EmployeeBloc>(context)
.add(DeleteEmployee(index)));
Navigator.pop(context,employee);
showSnackBar(context, index, employee);
}),
FlatButton(
child: Text('No!'),
onPressed: () => Navigator.pop(context)),
],
)
],
));
}
Instead, I thought I could return an employee from the showDeleteDialog when I confirm the deletion. When the result is not null, then I should show the SnackBar. I tried to implement this with Future/Async but with no success.
onPressed: () async {
Employee deletedEmployee = await showDeleteDialog(context, employee, index);
await showSnackBar(context, index, deletedEmployee);
},
Edit: I would like to avoid using GlobalKey if possible, since I read it is not good for the App's performance.
As the error says Scaffold.of() called with a context that does not contain a Scaffold., that means the current context that you are passing to showSnackBar() method doesn't contain a Scaffold in the immediate parent.
We can fix this by using GlobalKey and assign it to the Scaffold. Declare a global key in your stateful widget and pass this as a key in your Scaffold, as below:
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
....
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(widget.title),
),
I call a method _showSnackBar() after navigator.pop() on OK button click inside the alertDialog, as below:
return AlertDialog(
title: Text('Not in stock'),
content: const Text('This item is no longer available'),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
_showSnackBar();
},
),
],
);
Then in the _showSnackBar() method, use the key to show the snackbar, as below:
void _showSnackBar() {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text('Snackbar is displayed'),
));
}
With this approach, once you tap on OK button on alertDialog, the dialog will close and you'll see the snackbar. You may need to customize this per your code as you shared above.
Hope this answers your question and resolves your issue.
Found the solution and it is super easy...
I only had to re-name one of the context to a dialogContext
showDeleteDialog(BuildContext context, Employee employee, int index) {
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title:
Text('Are you sure you want to delete: ${employee.name} ?'),
actions: <Widget>[
Row(
children: <Widget>[
FlatButton(
child: Text('Yes'),
onPressed: () {
DatabaseProvider.db.deleteEmployee(employee.id).then(
(_) => BlocProvider.of<EmployeeBloc>(dialogContext)
.add(DeleteEmployee(index)));
Navigator.pop(dialogContext);
showSnackBar(context, index, employee);
}),
FlatButton(
child: Text('No!'),
onPressed: () => Navigator.pop(context)),
],
)
],
));
}

Can a Card widget display a ListTile?

In this app, when the user clicks the FAB, it returns a Card wrapped in a GestureDetector. when the user clicks on a displayed card, the GestureDetector will navigate them to another page. I want to implement a delete function so that i can dismiss a Card.
So i defined the child: of the Card as a ListTile with a trailing Icon which is supposed to delete that particular Card. But when i add a Card, it only displays the title: and does not display the trailing Icon.
The Cards are displayed in a SliverGrid with a crossAxisCount = 2.
question: do Card widgets support displaying a ListTile with a trailing widget or is my implementation wrong?
P.S. i have tried setting crossAxisCount = 1 but it still does not show the trailing Icon.
card generating function:
void addItems() async {
setState(() {
cardList.insert(0, new GestureDetector(
onTap: () async {
await Navigator.push(context, MaterialPageRoute(
builder: (context) =>
TodoList(), // this just navigates to another screen ; not important in this question
)
);
},
child: Card(
child: ListTile(
title: Text("project 1"),
trailing: new Icon(Icons.remove_circle,
color: Colors.redAccent,),
// subtitle: whitefontstylemont(text: "project 1", size: 20,)) //this is just a custom TextStyle
),
)
));
});
}
card deleting function:
_deleteNoDo(int index) async {
debugPrint("Deleted Item!");
setState(() {
cardList.removeAt(index);
});
}
complete example (excluding above mentioned functions):
class _Starting_screenState extends State<Starting_screen> {
int _count = 0;
#override
Widget build(BuildContext context) {
List<Widget> cardList = new List.generate(
_count, (int i) => new createCard());
SystemChrome.setEnabledSystemUIOverlays([]);
// _deleteNoDo(int index) async {
// debugPrint("Deleted Item!");
// setState(() {
// cardList.removeAt(index);
// });
// }
//
// void addItems() async {
// setState(() {
// cardList.insert(0, new GestureDetector(
// onTap: () async {
// await Navigator.push(context, MaterialPageRoute(
// builder: (context) =>
// TodoList(), // this just navigates to another screen ; not important in this question
// )
// );
// },
// child: Card(
// child: ListTile(
// title: Text("project 1"),
// trailing: new Icon(Icons.remove_circle,
// color: Colors.redAccent,),
//// subtitle: whitefontstylemont(text: "project 1", size: 20,)) //this is just a custom TextStyle
// ),
// )
// ));
// });
// }
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
setState(() {
_count += 1;
});
},
heroTag: "btn2",
child: Icon(Icons.add, color: Color(whitecolor),), // this is just a custom color
backgroundColor: Color(redcolor),), // this is just a custom color
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
flexibleSpace: FlexibleSpaceBar(
),
actions: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 20, right: 10),
child: whitefontstyle(text: "Remaining tasks for today - ${cardList.length}", size: 20,), // this is just a custom textstyle
),
),
],
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: new SliverChildBuilderDelegate((context,
index) {
return cardList[index]; // this is where the cards are displayed in a list
},
childCount: cardList.length
)
),
]
)
);
}
}
actual result:
expected result (assume only displaying title and trailing icon as shown below):
Are you sure that you are calling the right methods. Cause there are few things which are not in place or you haven't shared the right code. I will address them below.
First of all the alignment of child text in card. It is in center of the card but there is no center property used in your code. Card widget does not auto align the text.
Second adding the items. You post the code of addItems function but also added another function in build with name createCard. So we don't know if that createCard have the same widget tree as the addItems function or not.
Its not working not so you are not seeing it but even if you are able to see the Icon widget you still won't be able to delete the item. The reason being is its a non-clickable widget. You should be using the IconButton widget if you want to add the click functionality on it.
These are my finding in your code. If you can review them or either share the correct code, may be I am able to help more.

Create a new Card using onPressed()

When the user clicks the FAB, i want it to create a new Card widget wrapped in a GestureDetector. I have created a list of type widget and also created a function with a setState to add to the list and display it in a SliverGrid. but when i click the FAB, it doesnt seem to be adding.
Could i get a small guide on how to add a Card using an FAB. (or adding any type of widget in a SliverGrid).
the list : List<Widget> cardList = <Widget>[];
function for adding items to the list :
void addItems() async{
setState(() {
cardList.insert(0, GestureDetector(
onTap: () async {
await Navigator.push( context, MaterialPageRoute(
builder: (context) => TodoList(), // this just navigates to another screen ; not important in this question
)
);
},
child: Card(
child:Center(child: whitefontstylemont(text: "project 1", size:20,)) //this is just a custom TextStyle
),
));
});
}
the floating action button which adds a new Card to the list :
floatingActionButton: FloatingActionButton( // this is inside a Scaffold
onPressed: ()async {
setState(() {
addItems();
});
},
heroTag: "btn2",
child: Icon(Icons.add, color: Color(whitecolor),), backgroundColor: Color(redcolor),),
the SliverGrid where i want a new card to be displayed :
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2
),
delegate: new SliverChildBuilderDelegate((context, index) {
return cardList[index];
},
childCount: cardList.length // this is where i want a new Card to be displayed
)
),