JUN 06, 2020

We are back with a new post after a very long time. We hope that every one of you is keeping well in these testing times.

Today, we shall see how to create a BottomSheet with a custom SearchView for your Flutter app.

Go ahead and create a new Flutter project. After the project has been created successfully, your main.dart would look something similar to this:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      home: MyHomePage(title: 'Bottom sheet with search view'),


The MyHomePage class consists of a RaisedButton which will show a BottomSheet when clicked.


class _MyHomePageState extends State {
  List _tempListOfCities;
  final _scaffoldKey = GlobalKey();
  final TextEditingController textController = new TextEditingController();

  static List _listOfCities = [
    "New York",
    "Monte Carlo",

  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(widget.title),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
              child: Text("Show bottom sheet"),
              onPressed: () {

    void _showModal(context) {
          isScrollControlled: true,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
          context: context,
          builder: (context) {
            return StatefulBuilder(
                builder: (BuildContext context, StateSetter setState) {
              return DraggableScrollableSheet(
                  expand: false,
                      (BuildContext context, ScrollController scrollController) {
                    return Column(children: [
                          padding: EdgeInsets.all(8),
                          child: Row(children: [
                                child: TextField(
                                    controller: textController,
                                    decoration: InputDecoration(
                                      contentPadding: EdgeInsets.all(8),
                                      border: new OutlineInputBorder(
                                            new BorderRadius.circular(15.0),
                                        borderSide: new BorderSide(),
                                      prefixIcon: Icon(Icons.search),
                                    onChanged: (value) {
                                      setState(() {
                                        _tempListOfCities =
                                icon: Icon(Icons.close),
                                color: Color(0xFF1F91E7),
                                onPressed: () {
                                  setState(() {
                        child: ListView.separated(
                            controller: scrollController,
                            itemCount: (_tempListOfCities != null &&
                                    _tempListOfCities.length > 0)
                                ? _tempListOfCities.length
                                : _listOfCities.length,
                            separatorBuilder: (context, int) {
                              return Divider();
                            itemBuilder: (context, index) {
                              return InkWell(

                                  child: (_tempListOfCities != null &&
                                          _tempListOfCities.length > 0)
                                      ? _showBottomSheetWithSearch(
                                          index, _tempListOfCities)
                                      : _showBottomSheetWithSearch(
                                          index, _listOfCities),
                                  onTap: () {
                                            behavior: SnackBarBehavior.floating,
                                            content: Text((_tempListOfCities !=
                                                        null &&
                                                    _tempListOfCities.length > 0)
                                                ? _tempListOfCities[index]
                                                : _listOfCities[index])));


  Widget _showBottomSheetWithSearch(int index, List listOfCities) {
    return Text(listOfCities[index],
        style: TextStyle(color: Colors.black, fontSize: 16),textAlign: TextAlign.center);

  List _buildSearchList(String userSearchTerm) {
    List _searchList = List();

    for (int i = 0; i < _listOfCities.length; i++) {
      String name = _listOfCities[i];
      if (name.toLowerCase().contains(userSearchTerm.toLowerCase())) {
    return _searchList;


We have a few key points to unpack here. So let’s dive in.




  1. We are using a GlobalKey for ScaffoldState for the purposes of showing a SnackBar with the user selected item.
  2. We have created a static list of cities that will be used for this example.
  3. The _showModal() method is calling the showModalBottomSheet() which is returning a StateBuilder as the root Widget here. Now, you may be wondering why?


To quote the official doc:

A platonic widget that both has state and calls a closure to obtain its child widget.

A simpler explanation:

  • Whenever we use BottomSheet in our app, it creates a new Widget within the Navigator stack that isn’t a child of your original widget (MyHomePage in this case).
  • Without using the StatefulBuilder, the BottomSheet would never update with the latest data based on user search term.

The StatefulBuilder is the magic potion here that ties it all up.**

Now that we are clear on that, let’s continue.

  1. We are using the onChanged method within the TextField to track user input and build a _tempListOfCities based on the search term.
  2. The itemCount parameter uses either the _tempListOfCities if there is any search term, or the static _listOfCities. We are doing this so as to make sure the count is always up-to-date.
  3. The child parameter uses the same _tempListOfCities or _listOfCities based on what is available.
  4. Here, we are using the _scaffoldKey within its currentState to show the SnackBar. We are also using the behavior parameter here to make sure that the SnackBar is not blocked by the BottomSheet.
  5. The _showBottomSheetWithSearch simply draws each row item.
  6. The _buildSearchList is the method that performs the actual computation based on user input to generate the _tempListOfCities. For the purposes of this tutorial, the method is simply looping through the entire _listOfCities and comparing each input character with the original list, finding any items that contain the search term.
    In a real world application, there are better approaches than using a for loop for a large dataset. Please keep that in mind.

** There are some other ways of notifying the bottom sheet that a change has occurred besides a StatefulBuilder. You may also use a Listenable to notify the modal that its needs to update. Of course, if you plan to use Listenable make sure that you create a new Stateful or Stateless widget for better organization of your project. Check out ChangeNotifier or ValueNotifier depending on your requirement (if you want the updated value or not).

The final app looks something like this:

For the eagle eyed reader, you may have noticed that we have a DraggableScrollableSheet in the code. We quite like the idea of a DraggableScrollableSheet along with a SearchView that can be swiped up to cover the entire screen.

And that’s it! We managed to add a custom SearchView to our BottomSheet!

We are a mobile first development agency. Check our website or send us an email at pk@oaktreeapps.com with your thoughts or questions.