135

I am displaying a BottomSheet via showModalBottomSheet<Null>() and inside several widgets with a GestureDetector. I would like to see the BottomSheet closed not only by touching outside it but also after an onTap event of a GestureDetector inside. However, it seems the GestureDetector is not forwarding the touch event.

So I am wondering, is there a way to trigger the closing of the ModalBottomSheet programmatically or a way to tell the GestureDetector to forward the touch event?

Update (2018-04-12):

Following a code snippet for better understanding. The problem is that the ModalBottomSheet isn't closing when tapping on "Item 1" or "Item 2".

showModalBottomSheet<Null>(context: context, builder: (BuildContext context)
{
  return new SingleChildScrollView(child:
    new Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
      new GestureDetector(onTap: () { doSomething(); }, child:
        new Text("Item 1")
      ),
      new GestureDetector(onTap: () { doSomething(); }, child:
        new Text("Item 2")
      ),
    ]),
  );
});
JonasH
  • 3,960
  • 3
  • 15
  • 17

8 Answers8

217

Closing a ModalBottomSheet programmatically is done via

Navigator.pop(context);

So I just call that pop function inside the onTap callback function of the GestureDetector.

showModalBottomSheet(context: context, builder: (BuildContext context)
{
  return SingleChildScrollView(child:
    Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
      GestureDetector(onTap: () {
          Navigator.pop(context);
          doSomething();
        }, child:
        Text("Item 1")
      ),
      GestureDetector(onTap: () {
          Navigator.pop(context);
          doSomething();
        }, child:
        Text("Item 2")
      ),
    ]),
  );
});
JonasH
  • 3,960
  • 3
  • 15
  • 17
  • 1
    Great! Since doSomething method can also use Navigator and open new page, it is better to call "Navigator.pop(context)" before calling "doSomething()". – Bosko Popovic Jan 19 '19 at 08:28
89

Short answer:

Use any of the below:

Navigator.pop(context);
Navigator.of(context).pop();

Long answer:

Generally there are 2 types of bottom sheet.

  1. showModalBottomSheet => Like a Dialog, not a part of Scaffold

  2. showBottomSheet => Part of Scaffold, this is persistent.


1. Showing and Hiding showModalBottomSheet

This code shows bottom sheet, and hides it when tapping on the FlutterLogo

@override
void initState() {
  super.initState();
  Future(() {
    showModalBottomSheet(
      context: context,
      builder: (_) {
        return GestureDetector(
          onTap: () => Navigator.of(context).pop(), // Closing the sheet.
          child: FlutterLogo(size: 200),
        );
      },
    );
  });
}

Output:

enter image description here


2. Showing and Hiding showBottomSheet

This code shows a button, which will open and close the bottom sheet.

late PersistentBottomSheetController _controller;
GlobalKey<ScaffoldState> _key = GlobalKey();
bool _open = false;
  
@override
Widget build(BuildContext context) {
  return Scaffold(
    key: _key,
    body: Center(
      child: ElevatedButton(
        onPressed: () {
          if (!_open) {
            _controller = _key.currentState!.showBottomSheet(
              (_) => SizedBox(
                child: FlutterLogo(size: 200),
                width: double.maxFinite,
              ),
            );
          } else {
            _controller.close();
          }
          setState(() => _open = !_open);
        },
        child: Text(_open ? "Close" : "Open"),
      ),
    ),
  );
}

Output:

enter image description here

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • Late to the party, but in the second example, what do you need that `GlobalKey` for? – JJuice May 17 '21 at 17:40
  • 1
    @JJuice You need the `GlobalKey` to get the `ScaffoldState`. The other way to do it would be using the `Builder` and using `Scaffold.of(context).showBottomSheet(...)` – CopsOnRoad May 17 '21 at 21:04
6
class _FABState extends State<FAB> {
  bool isOpen = false;

  var bottomSheetController;

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        setState(() {
          isOpen = !isOpen;
        });
        print('tapped on the bottom sheet');
        if(isOpen) {
          bottomSheetController = showBottomSheet(
              backgroundColor: Colors.transparent,
              context: context,
              builder: (ctx) {
                return ClipRRect(
                  borderRadius: BorderRadius.only(
                    topRight: Radius.circular(20),
                    topLeft: Radius.circular(20),
                  ),
                  child: Container(
                    height: 150,
                    color: Colors.black,
                    child: TextField()
                  ),
                );
              });
          bottomSheetController.closed.then((value) {
            setState(() {
              isOpen = !isOpen;
            });
          });
        } else {
          Navigator.of(context).pop();
          setState(() {
            isOpen = !isOpen;
          });
        }
      },
      child: isOpen?Icon(Icons.arrow_downward):Icon(Icons.arrow_upward),
    );
  }
}

Video Representing the bottom sheet

Aashar Wahla
  • 2,785
  • 1
  • 13
  • 19
5

Showing and hiding bottom sheet in flutter:

 showModalBottomSheet(
    context: context,
    builder: (bCtx) {
      return GestureDetector(
        behavior: HitTestBehavior.opaque,
        child: Container(height: 300, child: Text('Bottom sheet widget'),),
        onTap: () {
          Navigator.of(context).pop();
        },
      );
    },
  );
Himanshu Mahajan
  • 4,779
  • 2
  • 36
  • 29
5

If your modal sheet doesn't goes down and gets back to the previous page directly, so you can use {useRootNavigator:true}, then use a Navigator.pop(context); from the other function. It will only hide the modal sheet instead of taking to the previous page.

double-beep
  • 5,031
  • 17
  • 33
  • 41
neon97
  • 552
  • 6
  • 8
5

Once your code has completed the task expected, you can add Navigator.of(context, rootNavigator: true).pop(); at the end so that the modal closes

Datapalace
  • 107
  • 1
  • 8
1

If you are using bottom sheet with scaffold key and persistent bottom sheet controller my way is like;

final scaffoldState = GlobalKey<ScaffoldState>();
dynamic controller;
        
if(controller!=null){
   controller as PersistentBottomSheetController).close();
   controller=null;
}
    
override
void dispose() {
   if(controller!=null){
      controller as PersistentBottomSheetController).close();
   }
   super.dispose();
}

in this way, for like my widget hierarchy; if user click the bottom navigation bar, user can change the current page, I am closing the current bottom sheet.

if user close the bottom sheet himself, for our dynamic controller, we can close the controller and assign it to null value. so this way, we can know is the bottom sheet exist or not.

Marcel Hofgesang
  • 951
  • 1
  • 15
  • 36
leylekseven
  • 687
  • 7
  • 15
0

Having tried all this options

Navigator.of(context, rootNavigator: true).pop();
Navigator.pop(context);
Navigator.of(context).pop();

I had to research why and when to use what. They will all work but on a specific usecase and condition has to be met first.

Lets break it down.

Navigator.of(context, rootNavigator: true).pop();

It's useful when dealing with multiple layers of navigation stacks and ideally when you have nested navigators. You would use this when you want to close the BottomSheet from the parent screen navigator context(root navigator)

For

Navigator.pop(context);
Navigator.of(context).pop();

This uses the default navigation context provided by the given 'context'. So if you are calling it from a widget inside the BottomSheet it will pop the closest navigator in the widget tree. Its much simpler and easy to use as it targets the correct context for the BottomSheet.

The difference is just based on how you reference the navigator and your coding style, but both will close the modal.

Danc-0
  • 51
  • 4