fluttermobile-applicationmobile-development

How to create this search bar in flutter?


How is the search bar, shown in the two attached screenshots, implemented? There is a map on the page, but when I click on the search bar, it displays the search field along with the search results, without navigating to a new page. The BottomNavigationBar remains visible, and the filter buttons (Pavilony, WC, etc.) disappear. I initially thought this was a SearchDelegate, but that would load an entirely new page. How is this behavior achieved?

Screenshots: page with search bar

search result


Solution

  • If you want to build a Custom searching by searching and get the result at the same screen you can implement this by building the UI and the management of the searching from search

    => Demo video for the provided Solution : CustomSearching

    I had a solution for the mechanism you are want to achieve

    1. Build CustomAppBarWidget

    
    class CustomAppBarWidget extends StatelessWidget {
      const CustomAppBarWidget({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: MediaQuery.sizeOf(context).height * .16,
          decoration: BoxDecoration(
            color: Colors.orange,
            borderRadius: BorderRadius.circular(20),
          ),
          child: Consumer<ManageSearch>(
            builder: (context, search, _) {
              return Column(
                children: <Widget>[
                  const SizedBox(height: 40),
                  Container(
                    margin: const EdgeInsets.all(10),
                    child: TextField(
                      controller: ManageSearch.searchController,
                      onChanged: (target) {
                        search.searchCity;
                      },
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.white,
                        hintText: "Search",
                        enabledBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15.0),
                          borderSide: const BorderSide(
                            color: Colors.grey,
                            width: 1.0,
                          ),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15.0),
                          borderSide: const BorderSide(
                            color: Colors.grey,
                            width: 1.5,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              );
            },
          ),
        );
      }
    }
    

    2. Build the DemoNavigationWidget just to simulate your UI

    
    class NavigationModel {
      final String label;
      final IconData icon;
    
      NavigationModel({
        required this.icon,
        required this.label,
      });
    }
    
    List<NavigationModel> navigations = <NavigationModel>[
      NavigationModel(icon: Icons.home, label: "Home"),
      NavigationModel(icon: Icons.person, label: "Profile"),
      NavigationModel(icon: Icons.settings, label: "Settings"),
      NavigationModel(icon: Icons.map, label: "Map"),
    ];
    
    class DemoNavigationBar extends StatelessWidget {
      const DemoNavigationBar({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(25),
            color: Colors.white,
          ),
          margin: const EdgeInsets.symmetric(horizontal: 20),
          padding: const EdgeInsets.all(10),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              for (int i = 0; i < navigations.length; i++) ...{
                Column(
                  children: <Widget>[
                    Icon(navigations[i].icon),
                    Text(navigations[i].label)
                  ],
                ),
              }
            ],
          ),
        );
      }
    }
    

    3. Build DemoFilterWidget also just to simulate your UI

    // Demo Filter
    
    List<ItemModel> filters = <ItemModel>[
      ItemModel(icon: Icons.wc, label: "WC"),
      ItemModel(icon: Icons.privacy_tip, label: "PR"),
      ItemModel(icon: Icons.restaurant, label: "rest"),
      ItemModel(icon: Icons.data_thresholding, label: "Data"),
    ];
    
    class DemoFilter extends StatelessWidget {
      const DemoFilter({super.key});
    
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          height: MediaQuery.sizeOf(context).height * .08,
          child: ListView.builder(
            itemCount: filters.length,
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) {
              return Container(
                width: MediaQuery.sizeOf(context).width * .25,
                padding: const EdgeInsets.all(10),
                margin: const EdgeInsets.all(10),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(15),
                  color: const Color(0xFFE7FBE6),
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Icon(filters[index].icon),
                    Text(filters[index].label),
                  ],
                ),
              );
            },
          ),
        );
      }
    }
    

    4. I used this demoList of cities to test

    
    List<String> cities = <String>[
      "Tokyo",
      "Delhi",
      "Shanghai",
      "São Paulo",
      "Mexico City",
      "Cairo",
      "Mumbai",
      "Beijing",
      "Dhaka",
      "Osaka",
      "New York",
      "Karachi",
      "Buenos Aires",
      "Chongqing",
      "Istanbul",
      "Kolkata",
      "Manila",
      "Lagos",
      "Rio de Janeiro",
      "Tianjin",
      "Kinshasa",
      "Guangzhou",
      "Los Angeles",
      "Moscow",
      "Shenzhen",
      "Lahore",
      "Bangalore",
      "Paris",
      "Bogotá",
      "Jakarta"
    ];
    

    5. Build a provider class to manage the search(SRP an clean) [IMPORTANT PART]

    
    class ManageSearch with ChangeNotifier {
      List<String> results = <String>[];
    
      static TextEditingController? searchController;
    
      void get searchCity {
        String target = searchController!.text;
    
        results = cities.where(
          (city) {
            bool isExist = city.toLowerCase().trim().contains(
                  target.toLowerCase().trim(),
                );
            return isExist;
          },
        ).toList();
        notifyListeners();
      }
    
      bool get startSearch {
        bool isUserSearch = searchController!.text.isNotEmpty;
        return isUserSearch;
      }
    
      bool get isEmptyResults {
        bool isResultEmpty = results.isEmpty;
        bool isEmpty = isResultEmpty && startSearch;
    
        return isEmpty;
      }
    
      bool get hasResult {
        bool hasResult = results.isNotEmpty && startSearch;
        return hasResult;
      }
    }
    

    6. Initialize the controller in the MainScreen

     @override
      void initState() {
        ManageSearch.searchController = TextEditingController();
        super.initState();
      }
    
      @override
      void dispose() {
        ManageSearch.searchController!.dispose();
        super.dispose();
      }
    

    7. Build the MainScreen and its components

    
    class CustomSearchScreen extends StatefulWidget {
      const CustomSearchScreen({super.key});
    
      @override
      State<CustomSearchScreen> createState() => _CustomSearchScreenState();
    }
    
    class _CustomSearchScreenState extends State<CustomSearchScreen> {
      @override
      void initState() {
        ManageSearch.searchController = TextEditingController();
        super.initState();
      }
    
      @override
      void dispose() {
        ManageSearch.searchController!.dispose();
        super.dispose();
      }
    
      double get height => MediaQuery.sizeOf(context).height;
    
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
          create: (context) => ManageSearch(),
          child: Consumer<ManageSearch>(builder: (context, search, _) {
            return Scaffold(
              resizeToAvoidBottomInset: false,
              body: Stack(
                alignment: Alignment.center,
                children: <Widget>[
                  if (search.isEmptyResults) ...{
                    Positioned.fill(
                      top: height * .17,
                      child: const Center(
                        child: Text(
                          "Empty Results",
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.bold,
                            color: Colors.red,
                          ),
                        ),
                      ),
                    ),
                  } else ...{
                    Positioned.fill(
                      top: height * .15,
                      child: search.startSearch
                          ? ListView.builder(
                              padding: const EdgeInsets.only(top: 20),
                              itemCount: search.results.length,
                              itemBuilder: (context, index) {
                                return Container(
                                  padding: const EdgeInsets.all(10),
                                  margin: const EdgeInsets.all(15.0),
                                  decoration: BoxDecoration(
                                    borderRadius: BorderRadius.circular(10),
                                    color: Colors.white,
                                  ),
                                  child: Row(
                                    mainAxisAlignment:
                                        MainAxisAlignment.spaceBetween,
                                    children: <Widget>[
                                      Text(
                                        search.results[index],
                                        style: const TextStyle(
                                          fontSize: 17,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                      const Icon(Icons.location_on)
                                    ],
                                  ),
                                );
                              },
                            )
                          : InteractiveViewer(
                              child: Image.asset(
                                "assets/images/others/profile/test_map.jpg",
                                fit: BoxFit.cover,
                              ),
                            ),
                    ),
                  },
                  const Positioned(
                    top: 0.0,
                    right: 0.0,
                    left: 0.0,
                    child: CustomAppBarWidget(),
                  ),
                  if (!search.startSearch) ...{
                    Positioned(
                      top: height * .14,
                      right: 0.0,
                      left: 0.0,
                      child: const DemoFilter(),
                    ),
                  },
                  Positioned(
                    right: 0.0,
                    left: 0.0,
                    bottom: 0.0,
                    child: Container(
                      height: height * .08,
                      decoration: const BoxDecoration(
                        borderRadius: BorderRadius.vertical(
                          top: Radius.circular(25),
                        ),
                        color: Colors.orange,
                      ),
                    ),
                  ),
                  Positioned(
                    bottom: height * .03,
                    right: 0.0,
                    left: 0.0,
                    child: const DemoNavigationBar(),
                  ),
                ],
              ),
            );
          }),
        );
      }
    }
    

    If you want you can use the setState((){}) instead the Provider but I used the Provider to build it clean

    You can control everything with the bools as Searching Status

    Hope that wan helpful for you