Searching through all kinds of various content in your Flutter app is something you'll likely have to implement one day. A successful search implementation needs basically two things - a usable search history and also the search bar widget. Now, we're going to take a look at the most extensible and good-looking way to do this.
The project we'll build
We're going to build a simple project that will contain all of the basic building blocks of a good search implementation. The package that we're going to use for the UI part is called material_floating_search_bar. There are many other packages and even the built-in showSearch function, but the package we're using is one of the best ones out there in terms of customizability and it also looks really good out of the box.
When it comes to the search history part, we're going to keep it simple. We could introduce all kinds of third-party dependencies like different state management packages and all kinds of persistent databases. However, we're going to implement the history only inside a StatefulWidget
and as an in-memory List
. Yet, as you'll see in a little while, the code will be easily applicable to any state management and database solution you like.
If you want to follow along with this tutorial, get the starter project from above which contains a bit of uninteresting yet necessary code already written for you.
Search history & logic
It's much simpler to implement the UI when you have the logic and state part out of the way. There are a bunch of things we should be able to do with the search history which are demonstrated in the video above.
- Add a search term and limit the maximum number of terms in history.
- Delete a search term.
- Get filtered search terms based on the user input.
- Reorder the recently searched-for term to be displayed first.
As I've already mentioned, we're going to use an in-memory List
and a StatefulWidget
. No matter which state mangement solution and database you want to use in your project, the principles will remain the same.
Inside the _HomePageState
class, let's create the following fields:
main.dart
static const historyLength = 5;
// The "raw" history that we don't access from the UI, prefilled with values
List<String> _searchHistory = [
'fuchsia',
'flutter',
'widgets',
'resocoder',
];
// The filtered & ordered history that's accessed from the UI
List<String> filteredSearchHistory;
// The currently searched-for term
String selectedTerm;
Filtering terms
As the user types in the search term, the history should display only the terms which start with the user input. In other words, typing in "fl" should only show terms such as "flutter" or "flask" because they start with "fl".
filterSearchTerms
will be a function that will take the raw _searchHistory
and output such a filtered list.
main.dart
List<String> filterSearchTerms({
@required String filter,
}) {
if (filter != null && filter.isNotEmpty) {
// Reversed because we want the last added items to appear first in the UI
return _searchHistory.reversed
.where((term) => term.startsWith(filter))
.toList();
} else {
return _searchHistory.reversed.toList();
}
}
List
to store the history data, we also use the filtering function to reverse the order of the list items. This will ensure that the last added terms will be displayed as first.Adding a search term
Adding search terms is not as simple as calling add
on a List
object. We want to ensure there are no duplicate terms in the history. If the term we're trying to add already exists in the history, we instead just want to reorder the already existing term to appear first in the UI.
Also, there's a limit on how many terms the history holds which we've set to 5, so we want to apply the so-called least recently used algorithm and remove the least recently used search term if we go over the history limit.
main.dart
void addSearchTerm(String term) {
if (_searchHistory.contains(term)) {
// This method will be implemented soon
putSearchTermFirst(term);
return;
}
_searchHistory.add(term);
if (_searchHistory.length > historyLength) {
_searchHistory.removeRange(0, _searchHistory.length - historyLength);
}
// Changes in _searchHistory mean that we have to update the filteredSearchHistory
filteredSearchHistory = filterSearchTerms(filter: null);
}
Deleting & reordering
While there's not much to say about deleting a term from history...
main.dart
void deleteSearchTerm(String term) {
_searchHistory.removeWhere((t) => t == term);
filteredSearchHistory = filterSearchTerms(filter: null);
}
Reordering or "putting the search term first" has an interesting implementation. At first we delete the term and then we add it again. Since newly added terms are displayed as first, this method serves its purpose well.
main.dart
void putSearchTermFirst(String term) {
deleteSearchTerm(term);
addSearchTerm(term);
}
String
s in the history. If you'd rather use your own class that's called, let's say, SearchTerm
, feel free to add a timestamp field to it which you can then update and base your sorting upon accordingly.Lastly, let's initialize the filteredSearchHistory
from initState
.
main.dart
@override
void initState() {
super.initState();
filteredSearchHistory = filterSearchTerms(filter: null);
}
Search bar UI
As you already know, we want to use the material_floating_search_bar package, so let's add it to the pubspec.
pubspec.yaml
dependencies:
flutter:
sdk: flutter
material_floating_search_bar: ^0.2.6
This package comes with a FloatingSearchBar
widget. Unlike the default AppBar
, this widget should be put into the Scaffold
's body
parameter. The content of the actual page, which in this case is the SearchResultsListView
from the starter project, is in turn put into the body
of the FloatingSearchBar
.
main.dart
@override
Widget build(BuildContext context) {
return Scaffold(
body: FloatingSearchBar(
body: SearchResultsListView(
searchTerm: selectedTerm,
),
),
);
}
Since we'll want to programmatically control the search bar, let's also set up the a controller for it.
main.dart
FloatingSearchBarController controller;
@override
void initState() {
super.initState();
controller = FloatingSearchBarController();
filteredSearchHistory = filterSearchTerms(filter: null);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FloatingSearchBar(
controller: controller,
body: SearchResultsListView(
searchTerm: selectedTerm,
),
),
);
}
To enable the commonly desired behavior where the search bar hides when the user scrolls down in a list, we need to wrap the search bar's body in a FloatingSearchBarScrollNotifier
.
main.dart
...
body: FloatingSearchBar(
controller: controller,
body: FloatingSearchBarScrollNotifier(
child: SearchResultsListView(
searchTerm: selectedTerm,
),
),
...
The FloatingSearchBar
also takes in the following cosmetic fields
main.dart
transition: CircularFloatingSearchBarTransition(),
// Bouncing physics for the search history
physics: BouncingScrollPhysics(),
// Title is displayed on an unopened (inactive) search bar
title: Text(
selectedTerm ?? 'The Search App',
style: Theme.of(context).textTheme.headline6,
),
// Hint gets displayed once the search bar is tapped and opened
hint: 'Search and find out...',
To easily clear the string which the user has typed in, there's the searchToClear
action.
main.dart
actions: [
FloatingSearchBarAction.searchToClear(),
],
Callbacks
The first callback function we're interested in is onQueryChanged
that gets triggered when a user types in characters. In our case, we just want to set the filteredSearchHistory
to be filtered with the currently typed-in string.
main.dart
onQueryChanged: (query) {
setState(() {
filteredSearchHistory = filterSearchTerms(filter: query);
});
},
onQueryChanged
such as a network call, consider setting the debounceDelay
so that the callback will not run for every single typed-in character.The second callback is onSubmitted
. Here we want to add and select the submitted query and also programmatically close the search bar.
main.dart
onSubmitted: (query) {
setState(() {
addSearchTerm(query);
selectedTerm = query;
});
controller.close();
},
Displaying (not only) the history
The builder
parameter of the FloatingSearchBar
is what holds the "search history" widget that appears when a user taps on the search bar. We have a completely free hand at how it's styled, so let's begin with making it rounded and white. We're going to use the Material
widget so that the ink splash effect will be visible once we add ListTile
s.
main.dart
builder: (context, transition) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
elevation: 4,
child: Placeholder(
fallbackHeight: 200,
),
),
);
},
We're not always going to display only the filteredSearchHistory
though. If the history is empty and the user has not typed in any search query yet, we want to display a "start searching" message. This means we'll write an if
statement and to run such conditional logic, we'll need to break out our code into another build
method with a Builder
(or create a new separate widget class, but I'm lazy ?).
main.dart
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.white,
elevation: 4,
child: Builder(
builder: (context) {
if (filteredSearchHistory.isEmpty &&
controller.query.isEmpty) {
return Container(
height: 56,
width: double.infinity,
alignment: Alignment.center,
child: Text(
'Start searching',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.caption,
),
);
}
},
),
),
);
Another special case that can occur is when the filtered history is empty and the user has already started typing in the search term. In such a case, we want to display the currently typed-in term in a single ListTile
. Tapping this tile behaves just like the onSubmit
callback we've already implemented.
Continuing in the inner builder
method from above...
main.dart
else if (filteredSearchHistory.isEmpty) {
return ListTile(
title: Text(controller.query),
leading: const Icon(Icons.search),
onTap: () {
setState(() {
addSearchTerm(controller.query);
selectedTerm = controller.query;
});
controller.close();
},
);
}
Lastly, in the else
clause, we want to display the filtered history in a Column
. While we technically could use a ListView
, we'd have to do a bunch of shenaningans. As the package docs say:
builder
is not allowed to have an unbounded (infinite) height. This is necessary in order for the search bar to be able to dismiss itself, when the user taps below the area of the child.Therefore, the easiest way to get a vertical list of widgets is to use a Column
with mainAxisSize
set to be the minimum.
The ListTiles
will have a delete button when the user taps them, the tapped search term will be selected and also put first in the history.
main.dart
else {
return Column(
mainAxisSize: MainAxisSize.min,
children: filteredSearchHistory
.map(
(term) => ListTile(
title: Text(
term,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
leading: const Icon(Icons.history),
trailing: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
deleteSearchTerm(term);
});
},
),
onTap: () {
setState(() {
putSearchTermFirst(term);
selectedTerm = term;
});
controller.close();
},
),
)
.toList(),
);
}
When you run the app now, it works! Searching for a term displays 50 search results in the SearchResultsListView
. Have you noticed something weird though?
Padding the content
The problem is that the first search result is not visible! The count starts from 0, but the only the item number 1 can be seen.
It so happens, that the very first ListTile
is perfectly hidden under the search bar but it's there! The solution is to add a padding to the ListView
inside of the SearchResultsListView
widget. It's best not to hardcode any values but instead to add padding based on the true height of the FloatingSearchBar
. We can easily look up the FloatingSearchBarState
that gives us access to the height and and vertical margin values with the familiar of
helper method.
main.dart
class SearchResultsListView extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
...
final fsb = FloatingSearchBar.of(context);
return ListView(
padding: EdgeInsets.only(top: fsb.height + fsb.margins.vertical),
children: List.generate(
50,
(index) => ListTile(
title: Text('$searchTerm search result'),
subtitle: Text(index.toString()),
),
),
);
}
}
Now even the very first search result is visible and you're fully set to use this kind of a search bar setup in your own apps. You should be able to take these methods and easily adapt them to your favorite state management solution and database.
full text search with inverted index
after upgrading to material_floating_search_bar: ^0.3.3
padding: EdgeInsets.only(top: fsb.height + fsb.margins.vertical),
error on the fsb. height and fsb.margins.vertical
Help?
Same here, if you found the solution could you share it?
Hi, you were able to solve that error, can you share the solution please, thanks!
My remote developers are right; the demand for freelance developers is continuously rising as the tech world evolves and businesses become increasingly reliant on technology. A developer’s income depends on the role and the software they are working on. You should check out Eiliana.com; they showcase your skills to the right people.
I must say this is a good post to read. Good job!
Magnificent beat ! I would like to apprentice while you amend your site, how can i subscribe for a blog web site? The account helped me a acceptable deal. I had been a little bit acquainted of this your broadcast offered bright clear idea
I loved as much as you will receive carried out right here. The sketch is attractive, your authored material stylish. nonetheless, you command get got an impatience over that you wish be delivering the following. unwell unquestionably come more formerly again since exactly the same nearly a lot often inside case you shield this hike.
Hi, i think that i saw you visited my web site thus i came to ?eturn the favor텶’m attempting to find things to enhance my site!I suppose its ok to use a few of your ideas!!
Usually I do not read article on blogs, however I would like to say that this write-up very compelled me to take a look at and do it! Your writing style has been amazed me. Thank you, very nice article.
of course like your website but you have to check the spelling on several of your posts. A number of them are rife with spelling issues and I in finding it very troublesome to inform the reality on the other hand I will certainly come back again.
Its like you read my mind! You appear to know so much about this, like you wrote the book in it or something. I think that you can do with a few pics to drive the message home a little bit, but instead of that, this is excellent blog. A fantastic read. I’ll certainly be back.
hello!,I really like your writing so a lot! share we keep up a correspondence extra approximately your post on AOL? I need an expert in this house to unravel my problem. May be that is you! Taking a look ahead to see you.
hi!,I like your writing so much! share we be in contact more approximately your article on AOL? I need a specialist in this area to resolve my problem. Maybe that is you! Looking ahead to see you.
I simply could not go away your web site prior to suggesting that I really enjoyed the standard info a person supply on your guests? Is going to be back incessantly to investigate cross-check new posts.
I have been surfing online more than 3 hours today, yet I never found any interesting article like yours. It is pretty worth enough for me. In my opinion, if all web owners and bloggers made good content as you did, the web will be much more useful than ever before.
Wow, wonderful blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is great, as well as the content!
I am not sure where you’re getting your info, but good topic. I needs to spend some time learning much more or understanding more. Thanks for magnificent info I was looking for this information for my mission.
I loved as much as you’ll receive carried out right here. The sketch is attractive, your authored material stylish. nonetheless, you command get bought an nervousness over that you wish be delivering the following. unwell unquestionably come more formerly again as exactly the same nearly a lot often inside case you shield this hike.
Thank you for the auspicious writeup. It in fact was a amusement account it. Look advanced to more added agreeable from you! By the way, how could we communicate?
Thanks, I have just been looking for information about this subject for a long time and yours is the best I’ve discovered till now. However, what in regards to the bottom line? Are you certain in regards to the supply?
Wonderful beat I wish to apprentice while you amend your web site how could i subscribe for a blog web site The account aided me a acceptable deal I had been a little bit acquainted of this your broadcast provided bright clear idea
Wow superb blog layout How long have you been blogging for you make blogging look easy The overall look of your site is magnificent as well as the content
Maximize Water Efficiency with Bwer Pipes’ Irrigation Solutions: At Bwer Pipes, we understand the importance of water conservation in Iraqi agriculture. That’s why our irrigation systems minimize water wastage while delivering precise hydration to your crops. Experience the difference with Bwer Pipes. Visit Bwer Pipes
helloI really like your writing so a lot share we keep up a correspondence extra approximately your post on AOL I need an expert in this house to unravel my problem May be that is you Taking a look ahead to see you
Hi Neat post Theres an issue together with your web site in internet explorer may test this IE still is the marketplace chief and a good component of people will pass over your fantastic writing due to this problem
Thank you for the good writeup It in fact was a amusement account it Look advanced to far added agreeable from you However how could we communicate
I was suggested this web site by my cousin Im not sure whether this post is written by him as no one else know such detailed about my trouble You are incredible Thanks
helloI really like your writing so a lot share we keep up a correspondence extra approximately your post on AOL I need an expert in this house to unravel my problem May be that is you Taking a look ahead to see you
Ny weekly You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
Techno rozen Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
Real Estate I really like reading through a post that can make men and women think. Also, thank you for allowing me to comment!
Fourweekmba I do not even understand how I ended up here, but I assumed this publish used to be great
Simplywall Good post! We will be linking to this particularly great post on our site. Keep up the great writing
I was recommended this website by my cousin I am not sure whether this post is written by him as nobody else know such detailed about my difficulty You are wonderful Thanks
Simply Sseven Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
I do not even know how I ended up here but I thought this post was great I do not know who you are but certainly youre going to a famous blogger if you are not already Cheers
Usually I do not read article on blogs however I would like to say that this writeup very compelled me to take a look at and do so Your writing taste has been amazed me Thanks quite nice post
Fiberglass and Resin Pipes in Iraq ElitePipe Factory in Iraq is proud to be a leading producer of high-quality fiberglass and resin pipes, delivering superior performance and durability for various industrial applications. Our fiberglass reinforced plastic (FRP) pipes, also known as GRP pipes, offer excellent corrosion resistance, lightweight properties, and long-lasting service life. These attributes make them ideal for use in demanding environments such as chemical processing, water treatment, and oil and gas industries. With our commitment to innovation and quality, ElitePipe Factory ensures that every pipe meets rigorous standards, establishing us as one of the best and most reliable suppliers in Iraq. For more information, visit our website at elitepipeiraq.com.
Fiber Optic Cable Conduit ElitePipe Factory in Iraq offers Fiber Optic Cable Conduits designed to protect and manage fiber optic cables with precision. Our conduits are constructed to ensure minimal signal loss and maximum protection against physical damage and environmental factors. With a focus on maintaining high performance and reliability, our fiber optic cable conduits are crucial for modern communication infrastructures. Renowned for our dedication to quality, ElitePipe Factory is one of the most reliable manufacturers in Iraq, providing conduits that meet international standards. Discover more about our products at elitepipeiraq.com.
Corrugated Metal Pipes in Iraq: At Elite Pipe Factory in Iraq, we also specialize in the production of Corrugated Metal Pipes, which are designed for strength and resilience in heavy-duty applications. Our corrugated metal pipes are constructed to handle high loads and resist environmental stresses, making them suitable for use in stormwater management, road construction, and culverts. With our commitment to quality and innovation, Elite Pipe Factory ensures that these pipes provide exceptional performance and durability. As one of the leading and most trusted factories in Iraq, we pride ourselves on offering products that exceed expectations. Learn more about our Corrugated Metal Pipes on our website elitepipeiraq.com.
شركة Bwer هي أحد الموردين الرئيسيين لموازين الشاحنات ذات الجسور في العراق، حيث تقدم مجموعة كاملة من الحلول لقياس حمولة المركبات بدقة. وتغطي خدماتها كل جانب من جوانب موازين الشاحنات، من تركيب وصيانة موازين الشاحنات إلى المعايرة والإصلاح. تقدم شركة Bwer موازين شاحنات تجارية وموازين شاحنات صناعية وأنظمة موازين جسور محورية، مصممة لتلبية متطلبات التطبيقات الثقيلة. تتضمن موازين الشاحنات الإلكترونية وموازين الشاحنات الرقمية من شركة Bwer تقنية متقدمة، مما يضمن قياسات دقيقة وموثوقة. تم تصميم موازين الشاحنات الثقيلة الخاصة بهم للبيئات الوعرة، مما يجعلها مناسبة للصناعات مثل الخدمات اللوجستية والزراعة والبناء. سواء كنت تبحث عن موازين شاحنات للبيع أو الإيجار أو التأجير، توفر شركة Bwer خيارات مرنة لتناسب احتياجاتك، بما في ذلك أجزاء موازين الشاحنات والملحقات والبرامج لتحسين الأداء. بصفتها شركة مصنعة موثوقة لموازين الشاحنات، تقدم شركة Bwer خدمات معايرة موازين الشاحنات المعتمدة، مما يضمن الامتثال لمعايير الصناعة. تشمل خدماتها فحص موازين الشاحنات والشهادات وخدمات الإصلاح، مما يدعم موثوقية أنظمة موازين الشاحنات الخاصة بك على المدى الطويل. بفضل فريق من الخبراء، تضمن شركة Bwer تركيب وصيانة موازين الشاحنات بسلاسة، مما يحافظ على سير عملياتك بسلاسة. لمزيد من المعلومات حول أسعار موازين الشاحنات، وتكاليف التركيب، أو لمعرفة المزيد عن مجموعة موازين الشاحنات ذات الجسور وغيرها من المنتجات، تفضل بزيارة موقع شركة Bwer على الإنترنت على bwerpipes.com
Bwer Company is a top supplier of weighbridge truck scales in Iraq, providing a complete range of solutions for accurate vehicle load measurement. Their services cover every aspect of truck scales, from truck scale installation and maintenance to calibration and repair. Bwer Company offers commercial truck scales, industrial truck scales, and axle weighbridge systems, tailored to meet the demands of heavy-duty applications. Bwer Company’s electronic truck scales and digital truck scales incorporate advanced technology, ensuring precise and reliable measurements. Their heavy-duty truck scales are engineered for rugged environments, making them suitable for industries such as logistics, agriculture, and construction. Whether you’re looking for truck scales for sale, rental, or lease, Bwer Company provides flexible options to match your needs, including truck scale parts, accessories, and software for enhanced performance. As trusted truck scale manufacturers, Bwer Company offers certified truck scale calibration services, ensuring compliance with industry standards. Their services include truck scale inspection, certification, and repair services, supporting the long-term reliability of your truck scale systems. With a team of experts, Bwer Company ensures seamless truck scale installation and maintenance, keeping your operations running smoothly. For more information on truck scale prices, installation costs, or to learn about their range of weighbridge truck scales and other products, visit Bwer Company’s website at bwerpipes.com.
dodb buzz There is definately a lot to find out about this subject. I like all the points you made
Masalqseen This is my first time pay a quick visit at here and i am really happy to read everthing at one place
I do not even know how I ended up here but I thought this post was great I dont know who you are but definitely youre going to a famous blogger if you arent already Cheers
FinTechZoomUs I truly appreciate your technique of writing a blog. I added it to my bookmark site list and will
Smartcric I do not even understand how I ended up here, but I assumed this publish used to be great
Blue Techker Nice post. I learn something totally new and challenging on websites
Blue Techker Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
Packachange This is my first time pay a quick visit at here and i am really happy to read everthing at one place
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.