Make showCupertinoModalPopup to return a value - flutter

Flutter reference documentation shows an example of using CupertinoPicker class wrapped by CupertinoModalPopupRoute popup Dialog:
https://api.flutter.dev/flutter/cupertino/CupertinoPicker-class.html (dartpad snippet available)
The Dialog with a CupertinoPicker pops up, once a button is clicked. Button's onPressed calls:
void _showDialog(Widget child) {
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
// some UI setting omitted
child: SafeArea(
top: false,
child: child,
),
));
}
where child is:
CupertinoPicker(
// some UI setting omitted
onSelectedItemChanged: (int selectedItem) {
setState(() {
_selectedFruit = selectedItem;
});
},
children:
List<Widget>.generate(_fruitNames.length, (int index) {
return Center(
child: Text(
_fruitNames[index],
),
);
}),
),
Method showCupertinoModalPopup used to push a Dialog with a Picker just above the main page.
Method's annotation mentions that "Content below the widget is dimmed with a [ModalBarrier]", and exactly ModalBarrier's handleDismiss method is called once user taps outside the Dialog's area, this leads to Navigator.pop being called, which pops the Dialog returning default void value (I cannot find a way to pass custom callback to the ModalBarrier)
Again from the showCupertinoModalPopup's annotation:
"Returns a Future that resolves to the value that was passed to
[Navigator.pop] when the popup was closed."
So the question is, what is the right way (if any) to override Navigator.pop to make showCupertinoModalPopup return a value? I don't want to use Picker's onSelectedItemChanged as it constantly changes the state while user choses right value.
Instead I would use it like:
Future<void> _showDialog(Widget child) async {
int? value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) => Container(
//omitted
child: SafeArea(
top: false,
child: child,
),
));
setState(() {
Provider.of<FruitsController>(context, listen: false).currentFruit = value;
});
}
I would appreciate your help.

Yes you can create a button to pop from showCupertinoModalPopup.
Future<void> _showDialog() async {
int? value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) => Column(
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(-2); //pasing value on pop is -2
},
child: Text("Close ")),
Container(
child: SafeArea(
top: false,
child: CupertinoPicker(
onSelectedItemChanged: (int selectedItem) {
_selectedFruit = _fruitNames[selectedItem];
},
itemExtent: 33,
children:
List<Widget>.generate(_fruitNames.length, (int index) {
return Center(
child: Text(
_fruitNames[index],
),
);
}),
),
),
),
],
),
);
print(value);
}

Related

Flutter alert box is not updating picked image from gallery

I am using an alert box where I am getting the image from gallery of the user, but the updated image is not getting displayed.
When I close the alert box and again open the alert box, then it's getting displayed. I am using provider package to handle the data.
Here is a video of what I am getting now
Here is my code:
child: ChangeNotifierProvider<MyProvider>(
create: (context) => MyProvider(),
child: Consumer<MyProvider>(
builder: (context, provider, child) {
return Column(
children: [
ElevatedButton(
onPressed: () {
showDialog(
barrierDismissible: true,
context: context,
barrierColor: Colors.black.withOpacity(0.5),
builder: (ctx) => AlertDialog(actions: <Widget>[
----> // alert box styling
Expanded(
child: Column(
children: [
CircleAvatar(
backgroundColor:
Colors.transparent,
radius: 175,
child: ClipOval(
child: provider
.image !=
null
? Image.network(
provider.image
.path,
height: 200,
)
: Image.asset(
'assets/profile.webp',
width: 250.0,
height: 250.0,
fit: BoxFit
.cover,
),
)),
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: <Widget>[
ElevatedButton(
onPressed: () async {
var image = await ImagePicker()
.pickImage(
source: ImageSource
.camera);
provider.setImage(
image);
},
child: Text(
'Use camera',
style: t3b,
),
),
},
child: const Text('Click me ')),
],
);
},
),
),
),
);
}
}
class MyProvider extends ChangeNotifier {
var image;
Future setImage(img) async {
image = img;
notifyListeners();
}
I am also facing the same issue in mobile development then I know we have to rebuild the whole dialog and then it will work well
showDialog(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0; // Declare your variable outside the builder
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
return Column( // Then, the content of your dialog.
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
// Whenever you need, call setState on your variable
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
Use a StatefulBuilder in the content section of the AlertDialog. Even the StatefulBuilder docs actually have an example with a dialog.
What it does is provide you with a new context, and setState function to rebuild when needed.
also sharing the reference I used for this: Reference for solving this same

How to dynamically change the bottomsheet height based on the lenght of the list in flutter

I have to use showModalBottomSheet for displaying list of menu item. But I am unable to dynamically set the height of the bottomsheet. Please help me to solve this issue. Thankyou.
This is how you can dynamically change the bottomsheet height based on the length of the list in the bottom sheet. But be aware of that you need to use statefulbuilder in the showModalBottomSheet to be able to setState the list.
List<int> myList = [1,2,3,4,5,6];
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, _setState){
return Container(
height: myList.length * 100,
color: Colors.amber,
child: ListView.builder(
itemCount: myList.length,
itemBuilder: (BuildContext ctxt, int index) {
return Row(
children: [
Text(myList[index].toString()),
TextButton(
onPressed: () {
_setState((){
myList = myList.where((value) => value != myList[index]).toList();
});
},
child: Text("delete"),
)
]);
}
)
);
}
);
},
);
},
),
);
}

Text widget doesn't appear in dialog after user Future.delayed in Flutter

I have a dialog that appears after user click on send button . When dialog appears , I want to show a text after 5 seconds . I use Future.delayed but the text doesn't appear at all . It appears only when I close the dialog and open it again . I want to show the text after 5 seconds from opening the dialog .
Here is my function
void _initialize() {
Future<void>.delayed(const Duration(seconds: 3), () {
if (mounted) {
setState(() {
visibility = true;
});
}
});
}
}
And here is my dialog code in the onTab of the button
_initialize()
showDialog(context: context,
barrierDismissible: false,
builder: (BuildContext contextd){
return WillPopScope(
onWillPop: () {return Future.value(false);},
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)
),
child: Padding(
padding: EdgeInsets.fromLTRB(20.w, 20.h, 20.w, 20.h),
child: Column(
children: [
//here are some widgets
Visibility(
visible:visibility?true:false,
child: Text("Resend?",style: TextStyle(decoration:
TextDecoration.underline,),)),
],),
),
),
);
});
Try using StatefulBuilder like this:
showDialog(
context: context,
builder: (BuildContext context) => StatefulBuilder(
builder: (context, setState) {
return //somthing to return;
},
));
This happens because your setState that update visibility has a different context than builder dialog. Visibility is actually update but only appear in dialog when close and open again because this is how build dialog is updated. If you want to work with the context dialog need to use that widget:
StatefulBuilder(
builder: (context, setState) {
Into your dialog and then call that setState.
Try using a StatefulBuilder. Using the bool visible and the setDialogState argument, you can update the state of the contents of the dialog.
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(
builder(context, setDialogState) {
// this variable is used for the conditional display
bool visible = false;
// this future will update the visible variable in 3 seconds, after the dialog is displayed already
Future.delayed(Duration(seconds: 3)).then(() => setDialogState(() { visible = true;}));
return WillPopScope(
onWillPop: () { return Future.value(false); },
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)
),
child: Padding(
padding: EdgeInsets.fromLTRB(20.w, 20.h, 20.w, 20.h),
child: Column(
children: [
//here are some widgets
Visibility(
visible:visible,
child: Text(
"Resend?",
style: TextStyle(
decoration: TextDecoration.underline,
),
),
),
],
),
),
),
);
},
);
};

How to refresh a ListViewBuilder when to click on a butotn?

I want to filter a listview by id or author using provider after i click on RaisedButton, but nothing seems to happen, that's what id did:
The refreshed button
#override
Widget build(BuildContext context) {
final todo_provider = Provider.of<TodoProvider>(context);
// TODO: implement build
return Scaffold(
body: Column(
children: <Widget>[
RaisedButton(
child: Text('Filter me'),
onPressed: () {
todo_provider.settNewss('John Biggs');
},
),
],
),
);
}
The result in ListView
#override
Widget build(BuildContext context) {
final todo_provider = Provider.of<TodoProvider>(context);
// TODO: implement build
return Container(
child: FutureBuilder<News>(
future: _future,
initialData: todo_provider.getnews,
builder: (context, snapshot) =>
snapshot.connectionState == ConnectionState.none ||
snapshot.data == null
? CircularProgressIndicator()
: Container(
height: 500.0,
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.articles.length,
itemBuilder: (context, index) => Text(
snapshot.data.articles[index].author.toString(),
),
),
),
),
);
}
try to wrap your onPressed function by a setState
onPressed: () {
setState() {
todo_provider.settNewss('John Biggs');
}
},
When using provider, to update data back to your Consumer you have to use
notifyListener();
Thus, under your settNewss() method, you probably need to use notifyListener() in the end to update the Consumer who are listening to this.
Tips: If you dont want to listen to the Provider on certain screens, you can use
final todo_provider = Provider.of<TodoProvider>(context, listen: false);
By doing this, your notifyListener() will not notify every widget that need this data.
I finally found a solution, is to update the future value on FutureBuilder:
OLD:
onPressed: () {
todo_provider.settNewss('John Biggs');
}
NEW:
onPressed: () {
_future = usersProvider.fetchUsers('John Biggs');
}
OR:
onPressed: () {
_future = Provider.of<TodoProvider>(context, listen: false)
.fetchUsers('John Biggs');
}

Flutter: CupertinoPicker BottomSheet listener for onClose?

I'm looking through the Flutter Gallery for the code related to the CupertinoPicker.
Here is the relevant code extract:
child: new GestureDetector(
// Blocks taps from propagating to the modal sheet and popping.
// onTap: () { },
child: new SafeArea(
child: new CupertinoPicker(
scrollController: scrollController,
itemExtent: _kPickerItemHeight,
backgroundColor: CupertinoColors.white,
onSelectedItemChanged: (int index) {
setState(() {
print(_selectedItemIndex);
_selectedItemIndex = index;
});
},
children: new List<Widget>.generate(coolColorNames.length, (int index) {
return new Center(child:
new Text(coolColorNames[index]),
);
}),
),
),
),
Now, I need a callback / listener for when the CupertinoPicker closes, in other words, when the user has made his selection and his selection is final, I need to know what his final selection is.
The use case here is that I want to make an api callback based on his final selection when the bottomsheet closes.
At the moment, I can only get the values as the picker is spun by the user as there is only a callback for onSelectedItemChanged. See gif below.
I see the BottomSheet widget has an onClosing callback: https://docs.flutter.io/flutter/material/BottomSheet-class.html
But I'm confused as to how I can get an instance of it to use as the Flutter Gallery sample is calling the bottomsheet using the following code and there is no method to get the bottomsheet from the code:
new GestureDetector(
onTap: () async {
await showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker();
},
);
},
child: _buildMenu(),
),
Does anyone know how I can get the callback listener for this?
EDIT - Based on Remi's solution, I have added the Navigator.of(context).pop(value) code within the onTap call back. However, the CupertinoPicker is not persistent so if the user touches outside the picker, the picker dismisses itself and a null value will be returned:
Widget _buildBottomPicker() {
final FixedExtentScrollController scrollController =
new FixedExtentScrollController(initialItem: _selectedItemIndex);
return new Container(
height: _kPickerSheetHeight,
color: CupertinoColors.white,
child: new DefaultTextStyle(
style: const TextStyle(
color: CupertinoColors.black,
fontSize: 22.0,
),
child: new GestureDetector(
// Blocks taps from propagating to the modal sheet and popping.
onTap: () { Navigator.of(context).pop(_selectedItemIndex);},
child: new SafeArea(
child: new CupertinoPicker(
scrollController: scrollController,
itemExtent: _kPickerItemHeight,
backgroundColor: CupertinoColors.white,
onSelectedItemChanged: (int index) {
setState(() {
// print(_selectedItemIndex);
// Navigator.of(context).pop(index);
_selectedItemIndex = index;
});
},
children: new List<Widget>.generate(coolColorNames.length, (int index) {
return new Center(child:
new Text(coolColorNames[index]),
);
}),
),
),
),
),
);
}
It's actually much simpler than what you thought.
showDialog and it's counterparts (including showModalBottomSheet) returns a Future that contains the result. So you can do the following :
final selectedId = await showModalBottomSheet<int>(...);
The only requirement is that when poping your modal/dialog/route/whatever, you do Navigator.of(context).pop(value) to send that value.
Like the previous answer said, the value needed is returned for you in the form of a Future. While that is true, it wasn't obvious to me how to implement that. In my case, I wanted to filter a todo list by a category list on the same screen. So by using the int key returned by onSelectedItemChanged, I was able to filter like this...
Widget _buildCategoryPicker(BuildContext context, _todoBloc) {
final FixedExtentScrollController scrollController =
FixedExtentScrollController(initialItem: _selectedIndex);
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<String>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker(
CupertinoPicker(
(...)
onSelectedItemChanged: (int index) {
setState(() => _selectedIndex = index);
},
children: (...),
), // CupertinoPicker
);
},
); // await showCupertinoModalPopup
// Filters the todoList.
_todoBloc.filter.add(categories[_selectedIndex]);
},
child: _buildMenu(
...
),
);
}
You do not need to set a final on your showCupertinoModalPopup or anything like this...
final selectedId = await showCupertinoModalPopup<String>(...);
like this:
future.whenComplete(() => requestRefresh());
showModalBottomSheet(
context: context,
builder: (_) {
return GestureDetector(
onTap: () => Navigator.of(context).pop(), // closing showModalBottomSheet
child: FlutterLogo(size: 200),
);
},
);