DEV Community

Nasir
Nasir

Posted on

Search Bars in Flutter

How To Make A functioning Search Bar in Flutter

So, you wanna make a search bar huh? Well you’re in luck, because, I’m going to guide you through just that. I know we all know what search bars are, but let’s look at what they really do, and how they do it.

The Search Bar

At its core, a search bar dynamically “filters” a list. Yeah, that’s it. Let’s dive into the code. We’ll be searching through a list of fruits that we’ll store in hive, so let’s go ahead and make a basic search bar.

import 'package:flutter/material.dart';
class MySearchBar extends StatelessWidget {
  const MySearchBar({
    super.key,
    });

  @override
  Widget build(BuildContext context) {
    return TextField(
    decoration: InputDecoration(
     labelText: "Search",
     focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: const Color.fromARGB(255, 29, 253, 36))),
     border: OutlineInputBorder(borderSide: BorderSide.none),
     enabledBorder: OutlineInputBorder(),
     prefixIcon: Icon(Icons.search)
    ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

So this is the structure of our custom search bar. In the inputDecoration of a textField, there are borders you can set based on the state of the search bar, but in our case we’re only going to be using three of those borders.

Think of the border parameter as the default border that flutter gives us when we don’t explicitly style our textfield. It’s usually a boring underline widget so I set its borderSide to none. Think of borderSide as a term for the actual border around the textfield. The enabledBorder is a border we can style as we please and it’s the border we see before we even click on the search bar. To top it all off, the focusedBorder is the border we see when we actually click on the search bar which makes it “active”

Making The Search Bar Actually Search

Well a search bar that doesn’t search is just a bar(or something). You’ve probably noticed that when you search stuff on platforms, content is returned in real-time. In flutter, we can’t use just a normal list — we have to make our list alive so we can make changes and the UI updates without us having to go through reloading hell.

import 'package:flutter/material.dart';
import 'package:search_bar_article/custom_widgets/my_search_bar.dart';
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
String searchQuery = ''; 
final ValueNotifier<List<String>> dynamicFruits = ValueNotifier(
  [
    "Apple",
    "Coconut",
    "Guava",
    "Banana"
  ]);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(actions: [
    SizedBox(
      width: 200,
      child: MySearchBar())
      ],),
      body: Column(
        children: [

        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

What’s that ValueNotifier thingy you ask? Well think of it as a special type of container that allows the UI to get redrawn immediately any change in the data it holds occur without us having to reload the app each time. — sorta like a stream. In our example, it’s storing a list of fruits (I’m no botanist so I probably added stuffs that aren’t fruits) that we’re going to display in our UI.

We need a string to capture the text from the search bar which we’ll call searchQuery and to make sure the searchQuery is equal to the text we are currently typing, we’re gonna need to add an onChanged function to MySearchBar. The onChanged function is at its core, a function that allows you to do stuff as the content inside the search bar changes or in layman’s terms, as you type.

import 'package:flutter/material.dart';
class MySearchBar extends StatelessWidget {
  final void Function(String) onChanged;
  const MySearchBar({
    super.key,
    required this.onChanged
    });

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: onChanged,
    decoration: InputDecoration(
     labelText: "Search",
     focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: const Color.fromARGB(255, 29, 253, 36))),
     border: OutlineInputBorder(borderSide: BorderSide.none),
     enabledBorder: OutlineInputBorder(),
     prefixIcon: Icon(Icons.search)
    ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

What “final void Function(String) onChanged;” means is this a void function i.e it doesn’t return anything, but, it accepts a String as an argument. If the function didn’t have any parameters, we wouldn’t need to write Function(String) and we’d just use a VoidCallBack instead.

 child: MySearchBar(onChanged: (value){
        setState(() {
          searchQuery = value;
        });
      },))
Enter fullscreen mode Exit fullscreen mode

We have to add an onChanged function to MySearchBar or we’ll get an error about incomplete parameters. In the onChanged function, that variable value is the live text in our search bar so we use setState to update our searchQuery string to the value each time you type in the search bar.

Setting up the UI

Phew…..well that was quite the long section, but this is where the real fun starts.

 body: Column(
        children: [
       Expanded(child: ValueListenableBuilder(
        valueListenable: dynamicFruits, 
        builder: (context, value, widget){
      final filteredFruits = value.where((fruit){
        final fruitMatch = fruit.toLowerCase().contains(searchQuery.toLowerCase());
        if (searchQuery.isEmpty){
          return true;
        }
        return fruitMatch;
      }).toList();
          return ListView.builder(
          itemCount: filteredFruits.length,
          itemBuilder: (context, index) {
            final fruit = filteredFruits[index];
            return Container(
              width: 400,
              decoration: BoxDecoration(
                color: Colors.grey
              ),
              child: Text(fruit),
            );
          }));
        }))
        ],
      ),
Enter fullscreen mode Exit fullscreen mode

We used a ValueListenableBuilder as it allows the UI to redraw each time the dynamicFruits list changes. Our list of fruits won’t change unless we filter it by using the .where() method.

The .where() method accepts a callback which must have an argument. The argument corresponds to each element in our list and in our case, strings. We create a Boolean called fruitMatch and all that does is it looks at the name of the fruit in lowercase (That’s why we used the .toLowerCase method) and checks if it “contains” the searchQuery in lowercase (That explains the .contains method). You might probably wondering why we have to convert dynamicFruits and the searchQuery to lowercase, and that’s because strings are case-sensitive; “Apple” and “apple” are two different strings so that’s why we used the .toLowerCase method. So by returning the fruitMatch Boolean, the .where method will effectively filter out elements that match our query but we have to return true if searchQuery is empty so that it’ll return all the elements. Then we hook that bad boy to a ListView builder so we can see our data in the UI.

Congratulations, you are now a searcher (its a word, I think) capable of creating functional search bars in your various apps. I hope you were able to add that search bar you’ve always wanted. Good luck!

Top comments (0)