Flutter/Dart display results in list with searchbar

Question:
Hi all,

I am new to Flutter and I have encountered a problem that I’m stuck on. Let’s say I have a default list of houses from an API, which doesn’t include the distance between the house and the user’s location. On my page, I have a list that displays these houses. When I allow access to the device location, everything works perfectly: the list is displayed, searching for items works, and it calculates the distance between each house and the user’s location.

However, the problem arises when the user denies access to their location. In this case, the app should still display the default list, but without the distance information. Currently, my code is displaying the default list when the user denies access, but it has some issues. The first search works fine and shows the list with the searched value. However, if I remove the search text and press the search icon again, the list becomes empty, it must return the default list instead. And then the search function is not working anymore and my hole list is empty.

I have been trying to solve this issue for hours but still haven’t found a solution. I hope someone can help me out. Thanks in advance to all!


Repl link:


My apiService:

final List<House> houses = [];

  Future<List<House>> fetchHouses() async {
    final response = await http.get(
      Uri.parse(baseUrl),
      headers: {'Access-Key': accesKey},
    );

    if (response.statusCode == 200) {
      //Success response parse and return data
      final List<dynamic> jsonData = jsonDecode(response.body);
      final DateFormat dateFormat = DateFormat('yyyy-MM-dd');

      for (var json in jsonData) {

        //convert from string to datetime
        final dateString = json['createdDate'];
        try {
          final dateTime = dateFormat.parse(dateString);
          final house = House(
            id: json['id'],
            image: json['image'],
            price: json['price'],
            bedrooms: json['bedrooms'],
            bathrooms: json['bathrooms'],
            size: json['size'],
            description: json['description'],
            zip: json['zip'],
            city: json['city'],
            latitude: json['latitude'].toDouble(),
            longitude: json['longitude'].toDouble(),
            createdDate: dateTime,
          );
          houses.add(house);
        } catch (e) {
          print('Error ApiService: $e');
          // Handle the error as needed
        }
      }
      return houses;
    } else {
      throw Exception('Something goes wrong, failed to load houses with api');
    }
  }

My controller

final ApiService apiService = ApiService();

  Future<List<House>> fetchSortedHouses() async {
    try {
      final List<House> houses = await apiService.fetchHouses();
      final Position? userLocation = await getCurrentLocation();
      print('Fetched houses controller: $houses');

      if (userLocation != null) {
        houses.forEach((house) {
          final double distance = calculateDistanceFromLocation(userLocation, house);
          house.distanceFromUser = distance;
        });
      } else {
        houses.forEach((house) {
          house.distanceFromUser = null;
        });
      }

      // Sort the houses by price (cheapest first)
      houses.sort((a, b) => a.price.compareTo(b.price));

      return houses;
    } catch (error) {
      // Handle error if needed
      print('Error fetching houses OverViewController: $error');
      return [];
    }
  }


  // Ask user for permission to share location
  Future<bool> authorizeLocationAccess() async {
    final LocationPermission permission = await Geolocator.checkPermission();

    if (permission == LocationPermission.denied) {
      final LocationPermission requestPermissionResult = await Geolocator.requestPermission();

      if (requestPermissionResult == LocationPermission.denied) {
        // Handle permission denied
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      // Handle permission permanently denied
      return false;
    }

    return true; // Location access granted
  }

  // Find user's current location
  Future<Position?> getCurrentLocation() async {
    try {
      final Position position = await Geolocator.getCurrentPosition();
      return position;
    } catch (e) {
      print('Error getting current location: $e');
      throw e; // Rethrow the exception to handle it elsewhere if needed
    }
  }

  // Calculate the distance between user's location and house's location
  double calculateDistanceFromLocation(Position userLocation, House house) {
    final double distance = Geolocator.distanceBetween(
      userLocation.latitude,
      userLocation.longitude,
      house.latitude,
      house.longitude,
    );

    // Convert distance to kilometers
    final double distanceInKm = distance / 1000;

    return distanceInKm;
  }

  // Filter houses
  List<House> filterHouses(List<House> houses, String searchText) {
    final lowerCaseSearchText = searchText.toLowerCase();
    return houses.where((house) {
      return house.city.toLowerCase().contains(lowerCaseSearchText) ||
          house.zip.toLowerCase().contains(lowerCaseSearchText);
    }).toList();
  }

My page

class _OverViewState extends State<OverView> {
  OverViewController overViewController = OverViewController();
  List<House> houses = []; // Store all houses
  List<House> filteredHousesList = []; // Store the filtered houses
  final List<House> defaultHouses = [];
  String searchText = '';

  @override
  void initState() {
    super.initState();
    getDefaultHouses();
    checkLocationPermission();
  }

  void getHouses() async {
    final List<House> fetchedHouses =
        await overViewController.fetchSortedHouses();
    setState(() {
      houses = fetchedHouses;
      filterHouses();
    });
  }

  void filterHouses()  {

    if (searchText.isEmpty) {
      setState(() {
        filteredHousesList = houses; // Display the original list
      });
    } else {
      final List<House> filteredHouses =
      overViewController.filterHouses(filteredHousesList, searchText);
      setState(() {
        filteredHousesList = filteredHouses; // Display the filtered list
      });
    }
  }

  void getDefaultHouses() async {
    final List<House> defaultHouses = await ApiService().fetchHouses();

    if (searchText.isEmpty) {
      setState(() {
        filteredHousesList = defaultHouses; // Display the default list
      });
    } else {
      final List<House> filteredHouses =
      overViewController.filterHouses(defaultHouses, searchText);
      setState(() {
        filteredHousesList = filteredHouses; // Display the filtered list
      });
    }
  }

  void checkLocationPermission() async {
    final bool locationAccessGranted = await overViewController.authorizeLocationAccess();

    if (locationAccessGranted) {
      await overViewController.getCurrentLocation();
      getHouses();
    }


    setState(() {

    });
    print("defautl houses:$filteredHousesList:");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
     
      body: Column(
        children: [
          Container(
            color: const Color(0xFFEBEBEB),
            child: Padding(
              padding: const EdgeInsets.all(10.0),
              child: Container(
                decoration: BoxDecoration(
                  color: Colors.grey,
                  borderRadius: BorderRadius.circular(10.0),
                ),
                child: Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 10.0),
                  child: Row(
                    children: [
                      Expanded(
                        child: TextField(
                          onChanged: (value) {
                            setState(() {
                              searchText = value;
                              // filterHouses();
                            });
                          },
                          decoration: InputDecoration(
                            hintText: 'Search for a home',
                            border: InputBorder.none,
                          ),
                        ),
                      ),
                      IconButton(
                        icon: Icon(Icons.search),
                        onPressed: () {
                          filterHouses();
                        },
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: filteredHousesList.length,
              itemBuilder: (context, index) {
                final house = filteredHousesList[index];
                String baseurl = ApiService.baseUrl;
                return Container(
                  padding: EdgeInsets.all(10),
                  color: const Color(0xFFEBEBEB),
                  child: GestureDetector(
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute(
                          builder: (context) => DetailHouse(house: house),
                        ),
                      );
                    },
                    child: Card(
                      color: Colors.white,
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(10.0),
                      ),
                      child: Container(
                        padding: const EdgeInsets.all(15),
                        child: Row(
                          children: [
                            ClipRRect(
                              borderRadius: BorderRadius.circular(10.0),
                              child: Image.network(
                                Uri.parse(baseurl)
                                    .resolve(house.image)
                                    .toString(),
                                height: 100,
                                width: 100,
                                fit: BoxFit.cover,
                              ),
                            ),
                            const SizedBox(width: 13),
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              mainAxisAlignment: MainAxisAlignment.start,
                              children: [
                                Text('\$' + house.price.toString()),
                                const SizedBox(height: 8),
                                Text(house.zip + ' ' + house.city),
                                const SizedBox(height: 40),
                                Row(
                                  mainAxisAlignment:
                                      MainAxisAlignment.spaceBetween,
                                  children: [
                                    Text(
                                      house.distanceFromUser != null
                                          ? '${house.distanceFromUser?.toStringAsFixed(0)} km'
                                          : '',
                                    ),
                                  ],
                                ),
                              ],
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
code snippet

You are updating the filteredHousesList from its current state but when the search text is empty, it should be reset to the original houses list, not the currently filtered one.

Try to change this

To this:

void filterHouses()  {
    if (searchText.isEmpty) {
      setState(() {
        filteredHousesList = houses; // Display the original list
      });
    } else {
      final List<House> filteredHouses =
      overViewController.filterHouses(houses, searchText); // Update to use houses instead of filteredHousesList
      setState(() {
        filteredHousesList = filteredHouses; // Display the filtered list
      });
    }
}

Hello friend,

i tried this, but then when im trying to search, my results return nothing, it return an empty list. And when i have a empty searchtext it must return the default list, now this is also empty then. And when im using the :

overViewController.filterHouses(filteredHousesList, searchText);

Then the search items is displaying correct. but when i have a empty searchtext, it returns also empty list. It must return the defaultHouses instead.

Hmmm…

Try to call the filterHouses function when you actually have a search text. Update yourIconButton onPressed

IconButton(
  icon: Icon(Icons.search),
  onPressed: () {
    if (searchText.isNotEmpty) {
      filterHouses();
    } else {
      setState(() {
        filteredHousesList = houses; // Return to the original list
      });
    }
  },
),

But still make the changes I made before.

Try this and tell me if it worked

I tried but it still have the same issue as above .

Is your project on replit?

Sent me the link if it is

no its not… i tried to put it on but its a litle big or something to upload my whole folder

Sorry for the delay. Had some things to do this weekend.

I’m glad that you manage to fix. Looking again, you are not changing the state after the permission is grant. You need to trigger a call to notify Flutter that it needs to rebuild your UI.

You need to modify your checkLocationPermission method to update the houses list.

void checkLocationPermission() async {
  final bool locationAccessGranted = await overViewController.authorizeLocationAccess();

  if (locationAccessGranted) {
    await overViewController.getCurrentLocation();
    getHouses(); // Refresh the list of houses based on the updated location
  }
}

And modify your getHouses method too, to fetch the houses, filter, update and make the call to trigger the Flutter

void getHouses() async {
  await overViewController.fetchSortedHouses();

  houses = overViewController.filterHouses(searchText);

  setState(() {}); //this will trigger the call to rebuild your UI
}

Try to see if this works

this works, but now its displaying 2 same list cause duplicates items.

Try to clear the houses list in the fetchSortedHouses method, I think everytime you call the function he adds new houses to the existing ones.

  Future<List<House>> fetchSortedHouses() async {
    try {
      houses.clear();  // Clear the houses list
      houses = await apiService.fetchHouses();
      final Position? userLocation = await getCurrentLocation();
      print('Fetched houses controller: $houses');

      if (userLocation != null) {
        houses.forEach((house) {
          final double distance = calculateDistanceFromLocation(userLocation, house);
          house.distanceFromUser = distance;
        });
      }

      // Sort the houses by price (cheapest first)
      houses.sort((a, b) => a.price.compareTo(b.price));

      return houses;
    } catch (error) {
      // Handle error if needed
      print('Error fetching houses OverViewController: $error');
      return [];
    }
  }

It can be another part of your code making the duplicates too, so this may not work.

Broo you are fkcing legend! Thanks mate everything works now! Thanks for taking time to help me out bro, i really appreciate it. You dont know how much time it took me haha.

hm… wait bro. Everythings works well but if i restart the application it still displaying 2 lists xD.

The only thing I can think is trying to clear the houses list before the filter, in your FilterHouses function

void filterHouses() {
    houses.clear(); // Clear the existing list before filtering, to try prevent duplicates

    List<House> filteredHouses = overViewController.filterHouses(searchText);

    houses.addAll(filteredHouses); // Add the filtered houses to the list

    setState(() {});
}

If this not works, it may be your API Service creating the duplicates

You could try to clear the houses list everytime you make the call to fetchHouses() ,

Future<List<House>> fetchHouses() async {
  houses.clear(); // Add this line to clear the list before fetching new things.

  final response = await http.get(
    Uri.parse(baseUrl),
    headers: {'Access-Key': accesKey},
  );

  return houses;
}

I have it bro. Its the the apiService, i moved the list inside the fetchHouses and not as instance. That did the trick :). Now im done thanks for your help mate!

1 Like

You’re welcome! Glad I could help!

If the topic is done mark my answer as Solution and good luck!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.