KhoPhi
KhoPhi

Reputation: 9517

Flutter onClosing callback for showModalBottomSheet

I have a showModalBottomSheet like the below, which I understand to inherit from BottomSheet (right?)

      showModalBottomSheet<void>(
        context: context,
        builder: (BuildContext context) {
          return Container(
            height: 260.0,
            child: Text('I am text')
          );
        },
      );

What I want to do:

I want to know (listen) when the modal is being closed, and act on it.

I've seen this onClosing callback: https://docs.flutter.io/flutter/material/BottomSheet/onClosing.html

How can I have a listener attached to the showModalBottomSheet, and then act accordingly when it fires?

Upvotes: 80

Views: 72207

Answers (12)

Nikhileshwar
Nikhileshwar

Reputation: 1684

showModalSheet allows specifying the animation controller that will be used by Flutter for animating the sheet.

This listener of animation controller can be used to listen for current status of modal sheet.

The animation flow of sheet is as follow:

While opening:

AnimationStatus.forward -> AnimationStatus.compeleted/Dismissed

While closing:

Animation status.reverse -> AnimationStatus.compeleted/Dismissed

1. Init an instance of Animation controller

Animation controller has to be non null and duration and reverseDuration has to be set. the default material sheet animations use 250 (sheet entry) and 200 (sheet exit).

    late AnimationController _sheetController;
    AnimationStatus? _previousStatus;
...
    @override
    void initState() {
      super.initState();
      _sheetController = AnimationController(
        vsync: this,
        duration: Duration(milliseconds: 250),
        reverseDuration: Duration(milliseconds: 200)
      );
    }

2. Pass the controller to transitionAnimationController of showModalBottomSheet

       GestureDetector(
           child: Text('Open'),
           onTap: () async {
              await showModalBottomSheet( 
                  transitionAnimationController: _sheetController,
          ...
...
    }

3. Listen to the animation state changes of the controller

The status indicate the state of the modal sheet. The previous status being reverse and current status as completed or dismissed indicates sheet has just been closed.

@override
    void didChangeDependencies() {
      super.didChangeDependencies();
      widget.sheetController?.addStatusListener(_onSheetAnimationChange);
    }

void _onSheetAnimationChange(AnimationStatus status) {
     if (_previousStatus == AnimationStatus.reverse) {
       if (status == AnimationStatus.completed ||
           status == AnimationStatus.dismissed) {
         onSheetClosed();
       }
     }

    _previousStatus = status;
 }

Upvotes: 0

Karthikeyan Veeramani
Karthikeyan Veeramani

Reputation: 101

When the bottomsheet is closed and the .whenComplete((){}) method is working and also .then((void value){}) method also use

whenComplete method

_void() {
    showModalBottomSheet<void>(
        context: context,
        backgroundColor: Colors.white,
        barrierColor: Colors.black.withOpacity(0.5),
        builder: (BuildContext context) {
          return Container();
        }).whenComplete(() {
      // call the function;
    });
  }

then method

_void() {
    showModalBottomSheet<void>(
        context: context,
        backgroundColor: Colors.white,
        barrierColor: Colors.black.withOpacity(0.5),
        builder: (BuildContext context) {
          return Container();
        }).then((value) {
     // call the function;
    });
  }

Upvotes: 1

Matthew Rideout
Matthew Rideout

Reputation: 8518

Using async / await on showModalBottomSheet() close

You can do this with async / await instead of callbacks.

As per the documentation, showModalBottomSheet()

Returns a Future that resolves to the value (if any) that was passed to Navigator.pop when the modal bottom sheet was closed.

This means that we can "await" the showModelBottomSheet() to complete, and then use the value returned by the Navigator.pop() function used to close the sheet.

Because this is "waiting" for the sheet to be closed, your next function won't run until it's closed. In this example, we just call the print() function, but that could be any function you need. No callbacks are necessary in this case.

Tip: if it's critical that the user tap a "save and close" button to close the bottom sheet (instead of just tapping outside of it), you should use isDismissible: false on the sheet.

In this example, we expect the sheet to return a string when it's closed.

Example Button To Open The showModalBottomSheet()

  FlatButton(
    child: Text("Show Bottom Sheet"),
    onPressed: () async {
      print("Showing bottom sheet...");
      String test = await showModalBottomSheet<String>(
        context: context,
        isDismissible: false,
        builder: (context) => Widget(),
      );
      print("Modal sheet closed with value: " + test);
    },
  ),

Example Button To Close The sheet, inside Widget()

FlatButton(
  child: Text("Save and Close"),
  onPressed: () {
    print("Closing sheet.");
    Navigator.pop(context, "This string will be passed back to the parent",);
  },
),

Example Log Outputs

flutter: Showing bottom sheet...
flutter: Closing sheet.
flutter: Modal sheet closed with value: This string will be passed back to the parent

Upvotes: 65

Luke Hutchison
Luke Hutchison

Reputation: 9190

Use the following:

showModalBottomSheet(
  context: context,
  builder: (context) => SomeWidget(),
)..whenComplete(() {
  // do something on close
);

Although note that the widget tree is not torn down yet when whenComplete is called, so don't call dispose() methods in there for controllers, etc. (use StatefulWidgets inside the bottom sheet instead).

Upvotes: 3

Confiance
Confiance

Reputation: 926

The showBottomSheet return a future. Future<T?> showModalBottomSheet({});

So we can use a bool variable to check and know if the showBottomsheet is still open or not. For example, you have this code with this bool isStillOpen define in the top of your code,

Future<void> openShowBottomSheet(BuildContext  ctext) async {
// set your variable and open your bottomSheet
isStillOpen = true;
await showModalBottomSheet<void>(
  context: ctext,
  builder: (BuildContext context) =>
      Container(height: 300, child: Center(child: Text('Hello'))));
isStillOpen = false;

// when the showBotom is close, the variable isStillOpen is set to false, so anywhere in your code you can check your variable to know if the bottomSheet is open or not. }

Upvotes: 2

iPatel
iPatel

Reputation: 47049

You can also achieve it by use of whenComplete function of showModalBottomSheet.

Let's see below code

showModalBottomSheet<void>(
        context: context,
        builder: (BuildContext context) {
          return Container(
            height: 260.0,
            child: Text('I am text')
        );
      },
).whenComplete(() {
  print('Hey there, I\'m calling after hide bottomSheet');
});

Upvotes: 161

Manoranjan
Manoranjan

Reputation: 1015

This is tested and verified on 28-01-2021

showBottomSheet(
  backgroundColor: Colors.black.withOpacity(0.5),
  context: context,
  builder: (context) {
    return Container(//your sheet code here);      
  },
).closed.whenComplete(() {
 //do whatever you want after closing the bottom sheet
});

Upvotes: 12

alexwan02
alexwan02

Reputation: 753

wrapper Container in WillPopScope widget

showModalBottomSheet<void>(
        context: context,
        builder: (BuildContext context) {
          return WillPopScope(
            onWillPop: () aysnc{
              // listener dismiss
              return true;
            }
            child : Container(
              height: 260.0,
              child: Text('I am text')
            ));
        },
      );

Upvotes: 6

Danilo Santos
Danilo Santos

Reputation: 452

showBottomSheet does not return a future, now it returns a PersistentBottomSheetController

  var _scaffoldKey = GlobalKey<ScaffoldState>();
  PersistentBottomSheetController persistentBottomSheetController;
 persistentBottomSheetController = _scaffoldKey.currentState.showBottomSheet<void>(
    (context)=>Container(

    )
 );
 persistentBottomSheetController.closed.then(_closeModal);
  void _closeModal(void value) {
    print('modal closed');
  }

Upvotes: 6

kengres
kengres

Reputation: 430

() async {

   // Save the bottomsheet in a variable

   var bottomSheet = showModalBottomSheet(
      context: context,
      builder: (context) => Container();
    );

   // Detect when it closes
   await bottomSheet.then((onValue) {
     print("value: $onValue");
   });

   // Do something here
}

Upvotes: 1

jujka
jujka

Reputation: 1217

I personally think the accepted answer is not as sleek as it can be. A wiser idea is to do it native Dart way without any extra complexity:

() async {
      final result = await showModalBottomSheet(context: context, builder: (_) =>

      );
}

It also works with Navigator.of(context).pushNamed(). result variable value in my case is defined by the value you pass back on Navigator.of(context).pop({value}). You can return any kind of object and then make a simple if statement to make sure data is the one you want:

if(result != null && result == true) { ... }

Upvotes: 4

AndDev
AndDev

Reputation: 1020

Perhaps it's not the best solution, but showModalBottomSheet return a "Future" so you can used it.

For example:

void _showModal() {
    Future<void> future = showModalBottomSheet<void>(
      context: context,
      builder: (BuildContext context) {
        return Container(height: 260.0, child: Text('I am text'));
      },
    );
    future.then((void value) => _closeModal(value));
}
void _closeModal(void value) {
    print('modal closed');
}

Upvotes: 78

Related Questions