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