91

This code is very simple: shows a modal bottom sheet and when the uses clicks the button, it increases the height of the sheet by 10.

But nothing happens. Actually, it only updates its size if the user "slides" the bottom sheet with it's finger (I belive that swipe causes a internal setState on the sheet).

My question is: how do I call the update state of a ModalBottomSheet?

showModalBottomSheet(
    context: context,
    builder: (context) {
      return Container(
        height: heightOfModalBottomSheet,
        child: RaisedButton(

            onPressed: () {
              setState(() {
                heightOfModalBottomSheet += 10;
              });

            }),
      );
    });
Daniel Oliveira
  • 8,053
  • 11
  • 40
  • 76

5 Answers5

348

You can use Flutter's StatefulBuilder to wrap your ModalBottomSheet as follows:

showModalBottomSheet(
    context: context,
    builder: (context) {
      return StatefulBuilder(
          builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
        return Container(
          height: heightOfModalBottomSheet,
          child: RaisedButton(onPressed: () {
            setState(() {
              heightOfModalBottomSheet += 10;
            });
          }),
        );
      });
});

Please note that the new setState will override your main widget setState but sure you can just rename it so you would be able to set state of your parent widget and the modal's

//This sets modal state
setModalState(() {
    heightOfModalBottomSheet += 10;
});
//This sets parent widget state
setState(() {
     heightOfModalBottomSheet += 10;
});
mmahgoub
  • 3,910
  • 3
  • 20
  • 19
  • 30
    This should be the correct answer. Thank you very much sir, you saved me a lot of time (I actually considered changing my approach but instead I made one more search to find the solution, and here you are, at the bottom of the page with the golden ticket)) – Amine Aug 02 '19 at 16:06
  • 7
    Excellent answer. This has allowed me to have a persistent BottomSheet that I can set to a custom size which I can animate to full screen and back thanks to StatefulBuilder and StateSetter. Using the global `setState` was forcing the BottomSheet to redraw with its default open transition. – Conti Aug 21 '19 at 05:52
  • 2
    Can you tell me why we need to use StatefulBuilder to set state insde showModalBottomSheet and why normal setState is not working for it? – Darshan Aug 07 '20 at 11:03
  • 2 days of struggle come to end with this answer – hiashutoshsingh Sep 11 '20 at 16:21
  • 1
    Please vote this as the correct answer so people don't get errors testing the wrong code which was marked as correct. – Adarsh Balu Oct 04 '20 at 08:07
  • @mmahgoub Any idea on my question here: https://stackoverflow.com/questions/65260744/is-there-a-way-to-set-a-periodic-timer-for-a-flutter-statefulbuilder-to-update-w – Eradicatore Dec 12 '20 at 01:51
  • MA7GOUB YOU MASTER CODER I WANNA SHAKE YOUR HAND – Rageh Azzazy Mar 15 '21 at 07:30
  • 1
    @mmahgoub mmahgoub - BTW how can we update variable value from Parent screen to Bottom sheet and Bottom sheet to Parent screen? Kindly suggest. Thanks a lot. – Kamlesh May 31 '21 at 16:34
  • I am trying to use rangeslider in bottomsheet and used statefulBuilder but it does not change value of slider or it is not moving at all. any idea how to solve it? – Shvet Jun 11 '21 at 11:06
  • Having used this, it doesn't exactly lead to good looking code. – DarkNeuron Nov 01 '21 at 10:04
  • Good answer. Works perfectly 2022 – Daniel Sogbey Jun 30 '22 at 08:46
  • This should be the correct answer 2023 January – alexalejandroem Jan 15 '23 at 02:23
34

You can maybe use the showBottomSheet from the ScaffoldState. read more here about this showBottomSheet.

This will show the bottomSheet and return a controller PersistentBottomSheetController. with this controller you can call controller.SetState((){}) which will re-render the bottomSheet.

Here is an example

PersistentBottomSheetController _controller; // <------ Instance variable
final _scaffoldKey = GlobalKey<ScaffoldState>(); // <---- Another instance variable
.
.
.
void _incrementBottomSheet(){
    _controller.setState(
        (){
            heightOfModalBottomSheet += 10;
        }
    )
}
.
void _createBottomSheet() async{
  _controller = await _scaffoldKey.currentState.showBottomSheet(
        context: context,
        builder: (context) {
           return Container(
               height: heightOfModalBottomSheet,
               child: RaisedButton(
               onPressed: () {
                  _incrementBottomSheet()
              }),
         );
      });
}
Lebohang Mbele
  • 3,321
  • 1
  • 15
  • 20
  • 2
    Except `bottomSheet` and `modalBottomSheet` are not same. – Chirag Arora Jan 21 '21 at 20:29
  • FYI, there is a more correct answer below (top voted), that doesn't require changing your approach from modal bottom sheet to persistent bottom sheet. – Jefferson Mar 31 '21 at 18:21
  • @ChiragArora I know that the two are not the same thing. Otherwise, they will have the same name if they were the same thing. If you understand English I suggest you read my answer again, and this time read to understand. – Lebohang Mbele Mar 16 '22 at 17:01
20

Screenshot:

enter image description here


Create a class:

class MyBottomSheet extends StatefulWidget {
  @override
  _MyBottomSheetState createState() => _MyBottomSheetState();
}

class _MyBottomSheetState extends State<MyBottomSheet> {
  bool _flag = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FlutterLogo(
          size: 300,
          style: FlutterLogoStyle.stacked,
          textColor: _flag ? Colors.black : Colors.red,
        ),
        RaisedButton(
          onPressed: () => setState(() => _flag = !_flag),
          child: Text('Change Color'),
        )
      ],
    );
  }
}

Usage:

showModalBottomSheet(
  context: context,
  builder: (_) => MyBottomSheet(),
);
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • 1
    Key is to call `setState` on a Widget that represents the content of the `BottomSheet` (in this example `MyBottomSheet`) and NOT on the widget that opens the BottomSheet (with `showModalBottomSheet`). See the following DartPad for a complete example: https://dartpad.dev/896181cf93370d29c7c0015207fde12c – Faber May 21 '21 at 16:06
  • @Faber I believe I wrote the same thing. – CopsOnRoad May 22 '21 at 05:48
  • I agree your example does the same and works correctly. Thanks for that. However, when I tried to understand your example it wasn't clear to me from where I need to call `showModalBottomSheet`, therefore I added the link to a runnable example. – Faber May 23 '21 at 08:00
  • @Faber Alright. Well, you just need to call the `showModalBottomSheet` method from a Button `onPress`, for example. – CopsOnRoad May 23 '21 at 08:02
  • BTW how can we update variable value from Parent screen to Bottom sheet and Bottom sheet to Parent screen? Kindly suggest. Thanks a lot. – Kamlesh May 31 '21 at 16:35
  • @Kamlesh For your first part, you can pass the value to the child using its constructor and for the second part you can use a `provider` or any other state management package, however, if your code doesn't require too much of things to handle, you can make a global variable too. – CopsOnRoad Jun 01 '21 at 05:37
  • Thanks CopsOnRoad for your suggestion. I was also thinking about global state solutions like provider or bloc etc. Thanks again :) – Kamlesh Jun 01 '21 at 07:25
  • 1
    For some I couldnt get stateful builder to work as suggested in other answers. This worked and provided more cleaner separate code for me. – West Oct 27 '21 at 03:08
  • I have some text that needs to update in a different class, same screen. How do I ensure the setstate called from the modalbottomsheet updates other classes? Even provider doesnt seem to work for me – West Nov 02 '21 at 07:46
  • @West It's difficult to suggest anything without seeing your code. – CopsOnRoad Nov 04 '21 at 07:28
11

Please refer to the below working code. I created a new Stateful widget(ModalBottomSheet) for the showModalBottomSheet. On button press, we are rebuilding the ModalBottomSheet only which is much cleaner now. We can use AnimationController if need animation for changing the height.

import 'dart:async';
import 'package:flutter/material.dart';

class ModalBottomSheet extends StatefulWidget {
  _ModalBottomSheetState createState() => _ModalBottomSheetState();
}

class _ModalBottomSheetState extends State<ModalBottomSheet>
    with SingleTickerProviderStateMixin {
  var heightOfModalBottomSheet = 100.0;

  Widget build(BuildContext context) {
    return Container(
      height: heightOfModalBottomSheet,
      child: RaisedButton(
          child: Text("Press"),
          onPressed: () {
            heightOfModalBottomSheet += 100;
            setState(() {});
          }),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    Future(() => showModalBottomSheet(
        context: context,
        builder: (context) {
          return ModalBottomSheet();
        }));
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Modal example"),
      ),
    );
  }
}

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(title: 'Flutter Demo', home: new MyHomePage());
  }
}
Dinesh Balasubramanian
  • 20,532
  • 7
  • 64
  • 57
  • 1
    This is not setting the state of the current modal, instead, creating a new one over the old. That causes a ugly effect every time the button is pressed. – Daniel Oliveira Sep 20 '18 at 03:35
  • I agree... That's why I am closing the previous one before increasing the height... But still the flicker effect will be there... – Dinesh Balasubramanian Sep 20 '18 at 03:53
  • This does not update the state of the modal itselt, just the content withint it. But I believe that is a good workaround that will work on many cases. Thank you! – Daniel Oliveira Sep 20 '18 at 15:03
8

create a separate StatefulWidget for the showModalBottomSheet(), like

 showModalBottomSheet(
    context: context,
    builder: (ctx) {
      return MapBottomSheet();
    });

Bottom Sheet Statefulwidget

class MapBottomSheet extends StatefulWidget {
  @override
  _MapBottomSheetState createState() => _MapBottomSheetState();
}

class _MapBottomSheetState extends State<MapBottomSheet> {
  List<String> places = [];

  void _setPlaces(String place) {
    setState(() {
      places.add(place);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black12,
      child: Column(
        children: [
          AppTextField(
            hint: "Search",
            onEditingComplete: () {},
            onChanged: (String text) {},
            onSubmitted: (String text) async {
              // Await the http get response, then decode the json-formatted response.
              var response = await http.get(Uri.parse(
                  'https://api.mapbox.com/geocoding/v5/mapbox.places/$text.json?access_token=pk.eyJ1IjoidjNyc2lvbjkiLCJhIjoiY2ttNnZldmk1MHM2ODJxanh1ZHZqa2I3ZCJ9.e8pZsg87rHx9FSM0pDDtlA&country=PK&fuzzyMatch=false&place=park'));
              if (response.statusCode == 200) {
                Map<String, dynamic> data = jsonDecode(response.body);
                print(data.toString());

                List<dynamic> features = data['features'];

                features.forEach((dynamic feature) {
                  setState(() {
                    _setPlaces(feature['place_name']);
                  });
                });
              } else {
                print('Request failed with status: ${response.statusCode}.');
              }
            },
          ),
          Expanded(
            child: Container(
              height: 250.0,
              width: double.infinity,
              child: ListView.builder(
                  itemCount: places.length,
                  itemBuilder: (ctx, idx) {
                    return Container(
                      child: Text(places[idx]),
                    );
                  }),
            ),
          ),
        ],
      ),
    );
    
  }
}
Rashid Iqbal
  • 1,123
  • 13
  • 13