9

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.

enter image description here

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]),
                );
              }),
            ),
          ),
        ),
      ),
    );
  }
Simon
  • 19,658
  • 27
  • 149
  • 217

4 Answers4

18

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.

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • H Remi, can you please tell me where to put the Navigator.of(context).pop(value) code? I just want the picker to appear, the user must select the value and then when the user dismisses the picker, the final selectedId must be determined. I'm not navigating away from the page so I'm confused as to where I must put the navigator. – Simon Apr 17 '18 at 09:51
  • Somewhere inside `_buildBottomPicker`. But I don't have that part – Rémi Rousselet Apr 17 '18 at 09:54
  • modals/dialogs are routes displayed on the top of other routes. So they are instantiated/removed with `Navigator` too – Rémi Rousselet Apr 17 '18 at 09:55
  • I have added the code to the question and added the Navigator part of the code to the onTap method but that requires the user to tap on the item he wants to select from the picker and the navigator will not be called if the user scrolls through the picker for his selection and then dismisses the picker by clicking outside the picker in the grey area of the screen. Is there a way to call the navigator when the picker is dismissed by the user when he touches the grey part of the screen? – Simon Apr 17 '18 at 09:58
  • Usually the user want to cancel his action when clicking in the shadow so you may want to reconsider. But you can use `WillPopScope` to have a `onWillPop` callback, Although it's not optimal – Rémi Rousselet Apr 17 '18 at 10:06
  • Thank you so much for this, was looking on how to return a value using that future returned by the function – Ralph Nov 24 '19 at 09:44
1

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>(...);
Jeff Frazier
  • 519
  • 4
  • 9
1

like this:

future.whenComplete(() => requestRefresh());
barbsan
  • 3,418
  • 11
  • 21
  • 28
wslaimin
  • 49
  • 2
0
showModalBottomSheet(
  context: context,
  builder: (_) {
    return GestureDetector(
      onTap: () => Navigator.of(context).pop(), // closing showModalBottomSheet
      child: FlutterLogo(size: 200),
    );
  },
 );
MD MEHEDI HASAN
  • 2,044
  • 2
  • 19
  • 34
  • 2
    Please provide at least a basic information about the code, how does it work, how does it solve the problem... – Ruli Jan 19 '21 at 16:25