Not able to delete in onDismissible - dart

I have a code, that uses dismissible in the listview (showes items from database). After dismissing an item it is supposed to show snackbar but it is not showing it and it seems that the dismissible is still part of the tree. Can you help me with that?
return ListView.builder(
itemCount: count,
itemBuilder: (BuildContext context, int position) {
final ThemeData theme = Theme.of(context);
return Dismissible(
key: Key(this.objs[position].id.toString()),
onDismissed: (direction) {
setState(() async {
int result = await helper.delete(this.objs[position].id);
});
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text(this.objs[position].title + "dismissed")));
},
background: Container(
color: Colors.red,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0)
)
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: getColor(this.objs[position].priority),
child: Text(this.objs[position].id.toString()),
),
title: Text(obj[position].title),
subtitle: Text(objs[position].date),
onTap: () {
debugPrint("Tapped on " + objs[position].id.toString());
navigateToDetail(this.objs[position]);
},
),
);
},
);
this is called inside a Scaffold. And objs is a list that contains all my objects from the database.
Here is my delete code that is called inside onDismissed:
Future<int> delete(int id) async {
Database db = await this.db;
var result = await db.rawDelete("DELETE FROM $tblT WHERE $colId=$id");
return result;
}
I've noticed if I delete one item, and immediately try to create another one (I have an option to insert to DB):
It sometimes throws the error: A dismissed Dismissible widget is still part of the tree
Update:
Moved the delete part, before setState and I am getting the error: A dismissed Dismissible widget is still part of the tree every time I swipe to dismiss

You could try the following code for the onDismissed: property.
The problem is the future inside the onDismissed function. We need to reorder the async and await keywords.
Anyway, take care with the timings when removing successive items.
onDismissed: (direction) async {
String title = this.obj[position].title;
await helper.delete(this.obj[position].id);
setState(() {});
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("$title dismissed")));
},
It also moves the async out of the setState() and stores the title to be used later by the SnackBar.

Inside content in SnackBar you can try :
Text(this.obj[position].title.toString() + "dismissed")

Related

Dismissible confirmDismiss in combination with new route navigation causes Flutter crash

The context:
I stumbled upon a minor crash while testing a ListView of Dismissibles in Flutter. When swiping a dismissible, a Dialog is shown using the confirmDismiss option, for confirmation. This all works well, however the UI crashes when testing an unlikely use case. On the page are several options to navigate to other (named) routes. When a dismissible is swiped, and during the animation an option to navigate to a new route is tapped, the crash happens.
How to replicate the crash:
Dismiss the Dismissible
During the animation that follows (the translation of the position of the dismissible), tap on an action that brings you to a
new route. The timeframe to do this is minimal, I've extended it in the example.
The new route loads and the UI freezes
For reference, this is the error message:
AnimationController.reverse() called after AnimationController.dispose()
The culprit is the animation that tries to reverse when it was already disposed:
package:flutter/…/widgets/dismissible.dart:449
Things I've tried:
Initially, I tried checking this.mounted inside the showDialog builder but quickly realised the problem is not situated there.
Another idea was to circumvent the problem by using CancelableOperation.fromFuture and then cancelling it in the dispose() method of the encompassing widget, but that was to no avail.
What can I do solve or at least circumvent this issue?
The code (can also be found and cloned here):
// (...)
class _DimissibleListState extends State<DimissibleList> {
int childSize = 3;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: childSize,
itemBuilder: (context, index) {
if (index == 0) {
return _buildNextPageAction(context);
}
return _buildDismissible();
},
),
);
}
Widget _buildNextPageAction(context) {
return FlatButton(
child: Text("Go to a new page"),
onPressed: () => Navigator.of(context).pushNamed('/other'),
);
}
Dismissible _buildDismissible() {
GlobalKey key = GlobalKey();
return Dismissible(
key: key,
child: ListTile(
title: Container(
padding: const EdgeInsets.all(8.0),
color: Colors.red,
child: Text("A dismissible. Nice."),
),
),
confirmDismiss: (direction) async {
await Future.delayed(const Duration(milliseconds: 100), () {});
return showDialog(
context: context,
builder: (context) {
return Dialog(
child: FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("Confirm dismiss?"),
),
);
},
);
},
resizeDuration: null,
onDismissed: (direction) => setState(() => childSize--),
);
}
}
I had almost same problem with confirmDismiss ,in my case I was using (await Navigator.push() ) inside of confirmDismiss to navigate to another screen but in return I faced this error :
AnimationController.reverse() called after
AnimationController.dispose()
so to solve my problem inside of confirmDismiss I call a future function out side of confirmDismiss (without await ) and then add return true or false after that function call to finish animation of confirmDismiss.

Flutter: Mark ListTile as selected in drawer

I want to mark the ListTile of the current page as selected but 2 days ago I'm looking for a general way to do it.
I saw examples like this one where you hardcode the tile ID and use a case to know which is the current Tile. My question is, what if I have, to exaggerate, 100 ListTiles? How do I change the selected attribute programmatically to the selected Tile? Or a more real case: I have a Drawer that changes shape in each release, so keeping a code with the hardcoded IDs is not useful. I hope you understand the idea.
I've been trying different solutions for days but none seems general enough to me.
Simple create enum class like below.
enum DrawerSelection { home, favorites, settings}
Create enum object and pass pre-defined value if you want, in my case i pass home as selected ListTile item. Like below code.
class _MyHomePage extends State<MyHomePage> {
DrawerSelection _drawerSelection = DrawerSelection.home;
Then in ListTile use selected property and change enum onTap() like below code.
ListTile(
selected: _drawerSelection == DrawerSelection.home,
title: Text('Home'),
leading: Icon(Icons.home),
onTap: () {
Navigator.pop(context);
setState(() {
_drawerSelection = DrawerSelection.home;
_currentWidget = MainWidget();
_appBarTitle = Text("Home");
});
},
),
ListTile(
selected: _drawerSelection == DrawerSelection.favorites,
title: Text('Favorites'),
leading: Icon(Icons.favorite),
onTap: () {
Navigator.pop(context);
setState(() {
_drawerSelection = DrawerSelection.favorites;
_currentWidget = FavoritesWidget();
_appBarTitle = Text("Favorites");
});
},
),
ListTile(
selected: _drawerSelection == DrawerSelection.settings,
title: Text('Settings'),
leading: Icon(Icons.settings),
onTap: () {
Navigator.pop(context);
setState(() {
_drawerSelection = DrawerSelection.settings;
_currentWidget = SettingsWidget();
_appBarTitle = Text("Settings");
});
},
),
i think it is just simple you can create a new class that have your data and the bool selected
class Post {
final bool selected;
var data;
Post({
this.selected,
this.data
});
}
now when you use LIstView.builder in the itemBuilder if list[index].selected is true then set the color to blue if not then let it white or whatever in the on tap or onpressed whatever you are you using save the last clicked index in a global variable(called savedIndex)-initialize it with (-1)- and change selected to true at the this list index,then if savedIndex wasnt equal -1 change list[savedIndex].selected=false.
global variable
int selectedIndex =-1;
and itemBuilder.
itemBuilder: (BuildContext _context, int i) {
return GestureDetector(
child:
Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(16.0),
color:_list[index].selected? Colors.blue:colors.white,
),
child: _buildRow(_list[index]),) ,
onTap: () {
setState(){
if(savedIndex!=-1){
list[savedIndex].selected=false
}
_list[index].selected=true;
savedIndex=index;
}
}
);
}

DropdownButton can't update it's value(null)

The full page code is very long but my DropdownButton widget code like this.
The problems are,
first: I can't update my selectedCity, it doesn't get an update. Also, the print function calls null, since my cityList data is like [new york, paris, london] etc...
second: flutter doesn't change focus from any TextField to DropdownButton fully. I mean, clicked TextField, then DropdownButton but focus reverts to that TextField after the button click. It is default action of Flutter?
List<dynamic> _cityList;
String _selectedCity;
#override
Widget build(BuildContext context) {
return DropdownButton(
value: _selectedCity,
style: TextStyle(
fontSize: 11,
color: textColor,
),
items: _cityList.map((city) {
return DropdownMenuItem<String>(
child: Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(city),
),
);
}).toList(),
onChanged: (String value) {
setState(() {
_selectedCity = value;
print(_selectedCity);
});
},
isExpanded: true,
);
}
Edit: The solution of resetting FocusNode after selecting an item from DropdownMenuItem is adding this line inside of setstate like this:
this: FocusScope.of(context).requestFocus(new FocusNode());
to here: onChanged:(){setSate((){here}}
I hope it will help you. I have modified your code a little bit
List<dynamic> _cityList;
String _selectedCity;
It will show the Dropdown Button and when you click on it and select any value showing in the print
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
body: ListView(
children: [
Column(
children: <Widget>[
DropdownButton<String>(
items: _cityList.map((dynamic value) {
return DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedCity = value;
print(_selectedCity);
});
},
),
],
),
],
),
);
}
for the focus problem you should use focusNodes one with the drop down list and another with the text field https://docs.flutter.io/flutter/widgets/FocusNode-class.html.

Change Variable values in Flutter

I'm trying to change some variables in different methos in Flutter, but the value isn't changed.
An example is something like:
enum UserPlaceStatusType { NONE, GOING, THERE, OUT, CANCELLED }
class PlaceCardState extends State<PlaceCard> {
UserPlaceStatusType _isOtherPlaceActive = UserPlaceStatusType.NONE;
Widget build(BuildContext context) {
return Card(
child: Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this._getBody(),
),
bottomNavigationBar: this._getBottomNavigationBar()));
}
List<Widget> _getBody() {
return [
Expanded(child: Text('test'), flex: 3),
Expanded(child: Text('test'), flex: 6),
Expanded(child: this._getActionsMenu(), flex: 1)
];
}
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
//It opens a simple dialog
child: this._getCurrentUserPlaceStatus());
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
return new GraphqlProvider(
client: new ValueNotifier(
Client(endPoint: 'GraphQLUrl', cache: new InMemoryCache()),
),
child: new Query(
'The GraphQL Query',
variables: {},
builder: ({
bool loading,
var data,
var error,
}) {
if (data != null && data['getCurrentUserPlaceStatus'] != null) {
this._isOtherPlaceActive = UserPlaceStatusType.THERE;
Navigator.pop(context, DialogActions.cancel);
return Container();
} else {
this._isOtherPlaceActive = UserPlaceStatusType.GOING;
Navigator.pop(context, DialogActions.cancel);
return Container();
}
},
));
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) async {
//here there is a validation but the variable value is the initial one, I mean NONE
if (this._isOtherPlaceActive == UserPlaceStatusType.GOING) {
//Cod to do
return;
}
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
this._isOtherPlaceActive = UserPlaceStatusType.NONE;
Navigator.pop(context);
}
});
}
}
I changed the variable value through some methods, but when I need to apply the validation, that's the initial value, it isn't changed, and I could not apply SetState method cuz it breaks the modal and throws an exception.
I will appreciate any feedback.
The method setState() can't be called inside a widget directly. I'm curious with your use of GrapQLProvider since it returns an empty Container() widget just to check the status of the data.
While I'm unfamiliar with the use of GraphQL, if the client that you're using inherits either a Stream or Future, it can be used to listen when the query is done.
Here's some snippets as demo. Let _testFuture() as the sample for a Future callback.
Future _testFuture() async{
return null;
}
Future can be listened to inside a Widget. When the request finishes, we have the opportunity to call setState().
_testFuture().then((value) {
// Check for values here
setState(() {
// Update values
});
});
Or if the request is set in a Stream, it's also possible to listen for Stream changes inside a Widget.
_streamController.add(_testFuture());
_streamController.stream.listen((event) {
// Check for values here
setState(() {
// Update values
});
});
This may not be the exact answer that you're looking for, but I hope this can guide you for a solution to your approach. I also found a GraphQL sample that uses ObservableQuery as a Stream that you can try.
Your code is very complex and should be refactored. Please notice how dialogs must be called.
enum DialogResult {ok, cancel}
caller_widget.dart
FlatButton(
child: Text('Open dialog'),
onPressed: () async {
// Call dialog and wait for result (async call)
final dialogResult = await showDialog<DialogResult>(
context: context,
builder: (context) => DialogWidget(),
);
if (dialogResult == DialogResult.ok) {
// do something
}
},
),
dialog_widget.dart
...
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.pop(context, DialogResult.ok), // DialogResult.ok returns
),
FlatButton(
child: Text('Cancel'),
OnPressed: () => Navigator.pop(context, DialogResult.cancel), // DialogResult.cancel returns
),
So you can return required value from dialog and set it to required variable.
P.S. Try to avoid use of old fashion then process of futures and use async/await.

Flutter - Why slider doesn't update in AlertDialog?

I doing a AlertDialog, so when I tried to insert Slider widget inside the state of value sound realy stranger, and this doesn't happens if Slider is outside of AlertDialog
new Slider(
onChanged: (double value) {
setState(() {
sliderValue = value;
});
},
label: 'Oi',
divisions: 10,
min: 0.0,
max: 10.0,
value: sliderValue,
)
The complete widget code of AlertDialog
Future<Null> _showDialog() async {
await showDialog<Null>(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: const Text('Criar novo cartão'),
actions: <Widget>[
new FlatButton(onPressed: () {
Navigator.of(context).pop(null);
}, child: new Text('Hello'))
],
content: new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('Deseja iniciar um novo cartão com quantos pedidos ja marcados?'),
new Slider(
onChanged: (double value) {
setState(() {
sliderValue = value;
});
},
label: 'Oi',
divisions: 10,
min: 0.0,
max: 10.0,
value: sliderValue,
)
],
),
),
);
}
);
}
and everything is under State class of StatefullWidget.
Its look like doesn't update the value and when try to change the value keep in same position.
Update 1
The problem is there are 2 required parameters in Slider (onChanged, value), So I shoud update this or UI keep quite, see the video how the aplication is running
Video on Youtube
Update 2
I've also opened a issue to get help with this at Github repository, if someone wants to get more information can go to issue #19323
The problem is that it's not your dialog that holds the state. It's the widget that called showDialog. Same goes for when you call setState, you are calling in on the dialog creator.
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
Instead, you should make your dialog stateful. Hold the data inside that dialog. And then use Navigator.pop(context, sliderValue) to send the slider value back to the dialog creator.
The equivalent in your dialog would be
FlatButton(
onPressed: () => Navigator.of(context).pop(sliderValue),
child: Text("Hello"),
)
Which you can then catch inside the showDialog result :
final sliderValue = await showDialog<double>(
context: context,
builder: (context) => MyDialog(),
)
I've come up with the same issue with a checkbox and that's my solution, even if it's not the best approach. (see the comment in the code)
Future<Null>_showDialog() async {
return showDialog < Null > (
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("title"),
content: Container(
height: 150.0,
child: Checkbox(
value: globalSearch,
onChanged: (bool b) {
print(b);
globalSearch = b;
Navigator.of(context).pop(); // here I pop to avoid multiple Dialogs
_showDialog(); //here i call the same function
},
)),
);
},
);
}
Easiest and least amount of lines:
Use StatefulBuilder as top widget of Content in the AlertDialog.
StatefulBuilder(
builder: (context, state) => CupertinoSlider(
value: brightness,
onChanged: (val) {
state(() {
brightness = val;
});
},
),
));
I had similar issue and resolved by putting everything under AlertDialog in to a StatefullWidget.
class <your dialog widget> extends StatefulWidget {
#override
_FilterDialogState createState() => _FilterDialogState();
}
class _<your dialog widget> extends State<FilterDialog> {
#override
Widget build(BuildContext context) {
return AlertDialog(
//your alert dialog content here
);
}
}
create a statefull class with the slider at the return time and the double value should declare inside the statefull class thus the setstate func will work.
here is an example i done this for my slider popup its same for alert dialog use can declare the variable as global thus it can be accessed by other classes
class _PopupMenuState extends State<PopupMenu> {
double _fontSize=15.0;
#override
Widget build(BuildContext context) {
return Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
onChanged: (value) {
setState(() {
print(value);
_fontSize = value;
});
},
),
);
}
}

Resources