52  comments

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();
  }
}
Because we're using a 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);
}
This approach is perfect if you want to store only simple Strings 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);
  });
},
If you're performing an expensive operation in 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 ListTiles.

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:

By default, the widget returned by the 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.

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a Flutter Developer at LeanCode and a developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

Flutter UI Testing with Patrol

Flutter UI Testing with Patrol
  • 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?

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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

  • 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!

  • 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

  • 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.

  • 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

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >