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.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
//new
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;
//1
final _scaffoldKey = GlobalKey();
final TextEditingController textController = new TextEditingController();
//2
static List _listOfCities = [
"Tokyo",
"New York",
"London",
"Paris",
"Madrid",
"Dubai",
"Rome",
"Barcelona",
"Cologne",
"Monte Carlo",
"Puebla",
"Florence"
];
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
RaisedButton(
child: Text("Show bottom sheet"),
onPressed: () {
_showModal(context);
},
),
],
),
),
);
}
void _showModal(context) {
showModalBottomSheet(
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(15.0)),
),
context: context,
builder: (context) {
//3
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return DraggableScrollableSheet(
expand: false,
builder:
(BuildContext context, ScrollController scrollController) {
return Column(children: [
Padding(
padding: EdgeInsets.all(8),
child: Row(children: [
Expanded(
child: TextField(
controller: textController,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8),
border: new OutlineInputBorder(
borderRadius:
new BorderRadius.circular(15.0),
borderSide: new BorderSide(),
),
prefixIcon: Icon(Icons.search),
),
onChanged: (value) {
//4
setState(() {
_tempListOfCities =
_buildSearchList(value);
});
})),
IconButton(
icon: Icon(Icons.close),
color: Color(0xFF1F91E7),
onPressed: () {
setState(() {
textController.clear();
_tempListOfCities.clear();
});
}),
])),
Expanded(
child: ListView.separated(
controller: scrollController,
//5
itemCount: (_tempListOfCities != null &&
_tempListOfCities.length > 0)
? _tempListOfCities.length
: _listOfCities.length,
separatorBuilder: (context, int) {
return Divider();
},
itemBuilder: (context, index) {
return InkWell(
//6
child: (_tempListOfCities != null &&
_tempListOfCities.length > 0)
? _showBottomSheetWithSearch(
index, _tempListOfCities)
: _showBottomSheetWithSearch(
index, _listOfCities),
onTap: () {
//7
_scaffoldKey.currentState.showSnackBar(
SnackBar(
behavior: SnackBarBehavior.floating,
content: Text((_tempListOfCities !=
null &&
_tempListOfCities.length > 0)
? _tempListOfCities[index]
: _listOfCities[index])));
Navigator.of(context).pop();
});
}),
)
]);
});
});
});
}
//8
Widget _showBottomSheetWithSearch(int index, List listOfCities) {
return Text(listOfCities[index],
style: TextStyle(color: Colors.black, fontSize: 16),textAlign: TextAlign.center);
}
//9
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())) {
_searchList.add(_listOfCities[i]);
}
}
return _searchList;
}
}
We have a few key points to unpack here. So let’s dive in.

- We are using a
GlobalKeyforScaffoldStatefor the purposes of showing aSnackBarwith the user selected item. - We have created a static list of cities that will be used for this example.
- The
_showModal()method is calling theshowModalBottomSheet()which is returning aStateBuilderas the rootWidgethere. 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
BottomSheetin our app, it creates a newWidgetwithin theNavigatorstack that isn’t a child of your original widget (MyHomePagein this case). - Without using the
StatefulBuilder, theBottomSheetwould 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.
- We are using the
onChangedmethod within theTextFieldto track user input and build a_tempListOfCitiesbased on the search term. - The
itemCountparameter uses either the_tempListOfCitiesif 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. - The
childparameter uses the same_tempListOfCitiesor_listOfCitiesbased on what is available. - Here, we are using the
_scaffoldKeywithin itscurrentStateto show theSnackBar. We are also using thebehaviorparameter here to make sure that theSnackBaris not blocked by theBottomSheet. - The
_showBottomSheetWithSearchsimply draws each row item. - The
_buildSearchListis 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_listOfCitiesand 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 Flutter app development agency. Check our website or send us an email at hello@oaktreeapps.com with your thoughts or questions.

