Listen to ModalBottomSheet close - dart

how to execute a block of code when the user close Modal Bottom Sheet ?
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return new MusicR();
},
)

You can assign your showModalBottomSheet into a Future.
What will happen is that the user will trigger the close action on the modal sheet and trigger the then callback on your future variable.
Example:
Future<void> bottomSheetAwaitClose = showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container();
},
);
bottomSheetAwaitClose.then((void value) => print ("Bottom sheet closed"));

Code below prints 'null' after closing bottom sheet
test() async {
dynamic x = await showModalBottomSheet(context: context, builder: (context) => Container(height: 200.0, color: Colors.green,) );
print('$x');
// some other actions
}

Related

flutter - unable to send API request with FutureBuilder

I am trying to make an API call using FutureBuilder in flutter but it seems like the request is not sent because I do not see the response printing. here is my future builder:
FutureBuilder(
future: authBloc.login(user, pass),
builder: (context, AsyncSnapshot snapshotItem) {
Map<String, dynamic> data = snapshotItem.data[0];
print(data['response']);
if (data.containsKey('id')) {
saveId(data['id']);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) {
return MainPage();
}));
}
if (data.containsKey("response")) {
if (data['response'] == false) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('An Error Has Occurred'),
content: Text(
'Please Make Sure That You Are Entering Valid UserName And Password'),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () =>
Navigator.of(context).pop(),
)
],
);
});
}
}
},
);
the authBloc.login(user, pass), part is login function in another file that makes the API call and I thought it is not necessary to include that file here.
Although I can't see the rest of your code, I think what you are trying to achieve should be done with an method and not a widget. When the user presses submit, call a function that basically contains what you wrote on that builder.
Calling navigator from within the builder is a bad idea. Any builder is expected to be called multiple times and in your case will lead to unexpected behaviour, that might be what you are seeing

Conditional Navigation inside Widget Tree

I have asked before but things have changed, today I realized there is a serious problem with the solution I got before. My algorithm a bit changed.
This is the new code:
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: _future,
builder: (context, snapshot) {
if (snapshot.hasData) {
print('BOOT VALUE IS ${snapshot.data}');
}
return !snapshot.hasData
? SplashScreen()
: snapshot.data ? HomePage() : FirstScreen();
},
),
);
}
With this solution, the BootScreen page functions execute every time while I navigate inside the pages I render conditionally inside FutureBuilder. So it's not best... I need to execute Navigation without any problem inside Future Builder, like this:
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: _future,
builder: (context, snapshot) {
if (snapshot.hasData) {
print('BOOT VALUE IS ${snapshot.data}');
}
return !snapshot.hasData
? SplashScreen()
: snapshot.data
? Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => HomePage()))
: Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => FirstScreen()));
},
),
);
}
It won't work of course, since return values are not a Widget. Any solution?
Edit: Thanks to Remi, I solved like this:
#override
void initState() {
final MainModel model = ScopedModel.of(context);
model.bootUp().then(
(value) => Future.delayed(
Duration(seconds: 1, milliseconds: 500),
() {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
value ? HomePage() : FirstScreen()));
},
),
);
super.initState();
}
Simply do not use FututeBuilder and manipulate the Future directly:
Future future;
future.then((value) {
Navigator.pushNamed(context, "/foo");
});
Bear in mind that you should not do this within the build method. Do this where you create your Future, typically initState

How to sort data from api?

There are two dropdown button with the list of countries and types of sport. If on them somsething is chosen it is need to show listTile with the leagues on it is chosen to the country and/or sport and if on them nothing is chosen - show all leagues.
But I get:
Dart Error: Unhandled exception:
setState () called after dispose (): _SportLigPageState # b5830 (lifecycle state: defunct, not mounted)
This is what happens if you see the widget tree (e.g.). This error can occur when a call is made. Dispose () callback. It is necessary to ensure that the object is still in the tree.
This can be a memory card if it’s not. To avoid memory leaks, consider dispose ().
Api with leagues: https://www.thesportsdb.com/api/v1/json/1/all_leagues.php:
class LigList extends StatefulWidget {
#override
_LigListState createState() => _LigListState();
}
class _LigListState extends State<LigList> {
String sport;
String country;
List data;
Future<String> getJsonData() async {
http.Response response;
if (sport != null) {
if (country != null) response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport&s=$country'), headers: {"Accept": "application/json"});
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport'), headers: {"Accept": "application/json"});}
else if (country == null){ response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php'), headers: {"Accept": "application/json"});}
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$country'), headers: {"Accept": "application/json"});
var convertDatatoJson = json.decode(response.body);
data = convertDatatoJson['leagues'];
return "Success";
}
static const menuItems = countriesList;
final List<DropdownMenuItem<String>> _dropDownItems = menuItems
.map((String CountruValue) =>
DropdownMenuItem<String>(
value: CountruValue,
child: Text(CountruValue),
),
).toList();
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(children: <Widget>[
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {});
},
);}),
SizedBox(width: 5),
FutureBuilder(
future: _getSports(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? DropdownButton(
value: sport,
hint: Text("Choose a sport league of which you want to find"),
items: snapshot.data,
onChanged: (value) {
sport = value;
print(sport);
setState(() {});
},
)
: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: CircularProgressIndicator());
}),
Flexible(
child:FutureBuilder(
future: getJsonData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return ListView.separated(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int i) {
return Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment
.stretch,
children: <Widget>[
ListTile(
title: Text(data[i]['strLeague']),
subtitle: Text(
data[i]['strSport']),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (
BuildContext context) =>
new ComandListScreen()
// (data[i])
));
},
),
]
)
)
);
});
}))
]),
),
);
}
}
Any assistance is very much appreciated.
There's a lot of things wrong with your code. The first child in your code is wrapped in a FutureBuilder but you're not using any Future functionality.
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {}); // Remove this line
},
);}),
In addition to that you also are calling setState() randomly in your onChanged callback with nothing inside of it. I'd suggest you take that widget out of the FutureBuilder and just use the DropdownButton on it's own.
Then also in this line
itemCount: data == null ? 0 : data.length,
You're using data, which is set in the future that you call there. You might want to read up on how to properly use the FutureBuilder widget. Just return the data object from your _getJsonData() Future because it's always returning "Success" anyway. Return the list you want from the Future and then access it using snapshot.data
And lastly there's literally only one setState call in there so remove it and you'll be fine. My assumption is that there's some additional dispose you're calling or navigating away and the app crashes. Will need a lot more info to figure out, but you'll have to fix the way you use Futures and the Future builder so we can ensure it's not because of latent threads coming back and setting the state once you've left the view you were on.

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;
});
},
),
);
}
}

ShowDialog using children is deprecated how to use it the other way?

I am trying to use showDialog in the following manner
showDialog(context: context,child:new Text("Hello Dialgo"));
The above works fine however it states that child parameter has been deprecated and the alternative way is to :
'Instead of using the "child" argument, return the child from a
closure '
'provided to the "builder" argument. This will ensure that the BuildContext '
'is appropriate for widgets built in the dialog.'
I am not sure what that means. Any simple example here would be appreciated.
Change it it
showDialog(
context: context,
builder: (_) => new Text("Hello Dialgo")
);
If you need the context from within the dialog change builder: (_) => to builder: (BuildContext context) =>
Because the Builder is a function handler, we need to create a function which accepts a single argument (BuildContext) and returns a Widget.
Syntax can either be:
(BuildContext context) => new Text('...');
or
(BuildContext context) {
return new Text('...')
}
They are equivalent, though the second one can have more than one line
See an example here: https://github.com/aqwert/flutter_auth_starter/blob/master/lib/core/dialogs/showError_dialog.dart
The child deprecated. If you look the this property, you can this warning.
Instead of using the "child" argument, return the child from a closure provided to the "builder" argument. This will ensure that the BuildContext is appropriate for widgets built in the dialog.
If you want to use builder, only write a function that returns your widget.
Example usage in my loader function
void showLoader(BuildContext context) {
showDialog(context: context, builder: (BuildContext context) => new ProgressHUD(
color: Colors.white,
containerColor: Theme.of(context).primaryColor,
));
}
Usage
// Start to show loader
showLoader(context);
// Do a async job and wait it
await do();
// Hide the loader
Navigator.pop(context);
We can assign text widget to the alert variable, like this:
var alert = new Text("Hello dialog");
Since the child is deprecated:
showDialog(context: context, child: alert);
we can write it like this:
showDialog(context: context, builder: (_) => alert);
If you want to create more complex dialog, you can redefine alert like this:
var alert = new AlertDialog(
title: new Text('App'),
content: new Text(message),
actions: <Widget>[
new FlatButton(onPressed: () {Navigator.pop(context);},
child: new Text('OK'))
],
);
and use it the same as above.
This worked for me.
showDialog(
context: context,
builder: (BuildContext context) => new AlertDialog(
title: new Text('Warning'),
content: new Text('Hi this is Flutter Alert Dialog'),
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.close),
onPressed: () {
Navigator.pop(context);
})
],
));

Resources