Reputation: 1063
I have stored latitude and longitude for items in firestore database (the fields are: item_latitude and item_longitude). Therefore, all items have latitude and longitude. I can use a stream to get the items for example:
Stream<QuerySnapshot> getItems() async* {
yield* FirebaseFirestore.instance.collection("items").snapshots();
}
Using a StreamBuilder or FutureBuilder, I can get the items individual properties such as the latitude and longitude. Geolocator has a method to calculate distance which is also a future:
double distance = await geolocator.distanceBetween(lat, long, lat1, long1);
I am able to get the users current location and in this case it is lat1, long1 (which is an individual record). The problem is: the Strem getItems gets a stream of latitudes and longitudes and for each item, I need to calculate its current distance in reference to the current location. This means, as I iterate through the items for example in a GridView, I need to calculate and display the distance. I have written this question in an abstract manner so that the answer will address how to do an asynchronous calculation based on a sychronous stream of data such that while the data is displayed in the build section of the page, the calculations are done outside because as it is not, the build will not accept an asychronous calculation with synchronous. My attempts have led to the following: First Attempt:
child: StreamBuilder(
stream: FetchItems().getItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("KE");
}
if (snapshot.hasData) {
DocumentSnapshot data = snapshot.data.docs[index];
double lat = data.data()[Str.ITEM_LATITUDE];
double long = data.data()[Str.ITEM_LATITUDE];
return
Text(getDistance(usersCurrentLocationLat,usersCurrentLocationLong,lat,long).toString());
//This fails and returns on the Text place holder the following: Instance of 'Future<dynamic>'
}
}),
My second attempt is as follows:
child: StreamBuilder(
stream: FetchItems().getItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("KE");
}
if (snapshot.hasData) {
DocumentSnapshot data = snapshot.data.docs[index];
double lat = data.data()[Str.ITEM_LATITUDE];
double long = data.data()[Str.ITEM_LATITUDE];
double x = getDistance(usersCurrentLocationLat,usersCurrentLocationLong,lat,long);
return Text(x.toString());
//This fails and gives erro: type 'Future<dynamic>' is not a subtype of type 'double'
}
}),
Further investigation shows that the below method which is used to get current location and is also referenced in the iniState does actually get the values (assuming Gps is enabled offcourse):
_getUserCurrentLocation() {
final Geolocator geolocator = Geolocator()..forceAndroidLocationManager;
geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best).then(
(Position position) {
setState(
() {
_currentPosition = position;
usersCurrentLocationLat = _currentPosition.latitude;
usersCurrentLocationLong = _currentPosition.longitude;
//a system print here returns current location as 0.3714267 32.6134379 (same for the
//StreamBuilder)
},
);
},
).catchError((e) {
print(e);
});
}
Below is the method for calculcating distance - using the provided Geolocator distanceBetween() method.
getDistance(double lat, double long, double lat1, double long1) async {
return distance = await geolocator.distanceBetween(lat, long, lat1, long1);
}
@override
void initState() {
super.initState();
_getUserCurrentLocation();
}
How will I be able to iterate through the items getting their latitude and longitudes, calculate the distance and display it on the text? This is a general question, possible solution combinations will be very welcome. Note in the StreamBuilder, am actually able to print to console the coordinates for each using the below:
print("FROM CURRENT LOCATION HERE ----" + usersCurrentLocationLat.toString() +"::::::::" +
usersCurrentLocationLong.toString());
print("FROM STREAM FROM DB SURE ----" + lat.toString() +"::::::::" + long.toString());
Which prints in console for all the items in the db as (one example):
I/flutter (30351): FROM CURRENT LOCATION HERE ----0.3732317::::::::32.6128083
I/flutter (30351): FROM STREAM FROM DB SURE ----2.12323::::::::2.12323
Proving that the coordinates are actuall got. The main error: type 'Future' is not a subtype of type 'double' remains and the Text to display the distance is stretched red. Guide if one can on the best approach - it may help someone in the future too.
Upvotes: 2
Views: 2128
Reputation: 1287
My approach is as follows:
Query the whole documents in the following manner:
//This a synchronus operation
final data = await Firestore.instance
.collection('collection_name')
.getDocuments();
Then pass all the documents into a list:
Since DocumentSnapshot
is a List<dynamic>
List doc = data.documents;
now calculate the distance for each LatLng
in your firestore
with the current location LatLng
and go on appending it to an empty list
which on iteration you can use in a gridView
:
List distanceList=[]; //define and empty list
doc.forEach((e){
double lat=e.data[ITEM_LATITUDE]; //asuming ITEM_LATITUDE is the field name in firestore doc.
double lng=e.data[ITEM_LONGITUDE];//asuming ITEM_LONGITUDE is the field name in firestore doc.
distanceList.add(your distance calculating function); //call this inside an async function if you are using await;
});
Instead of using an Async gelocator.distance
I have written a function in dart
using the ‘haversine’ formula to find the shortest function:
double calculateDistance (double lat1,double lng1,double lat2,double lng2){
double radEarth =6.3781*( pow(10.0,6.0));
double phi1= lat1*(pi/180);
double phi2 = lat2*(pi/180);
double delta1=(lat2-lat1)*(pi/180);
double delta2=(lng2-lng1)*(pi/180);
double cal1 = sin(delta1/2)*sin(delta1/2)+(cos(phi1)*cos(phi2)*sin(delta2/2)*sin(delta2/2));
double cal2= 2 * atan2((sqrt(cal1)), (sqrt(1-cal1)));
double distance =radEarth*cal2;
return (distance);
}
This a synchronous function
and the code would be as follows inside the firestore
doc.forEach();
list function:
List distanceList; //define and empty list
doc.forEach((e){
double lat=e.data[ITEM_LATITUDE]; //asuming ITEM_LATITUDE is the field name in firestore doc.
double lng=e.data[ITEM_LONGITUDE];//asuming ITEM_LONGITUDE is the field name in firestore doc.
double distance = calculateDistance(currentLat,currentLng, lat,lng);
distanceList.add(distance);
});
//Now the distanceList would contain all the shortest distance between
// current LatLng and all the other LatLng in your firestore documents:
Rember to empty the distanceList
before using to store the distances again.
The Whole Code would be as follows;
//Shortest Distance Function definition:
double calculateDistance (double lat1,double lng1,double lat2,double lng2){
double radEarth =6.3781*( pow(10.0,6.0));
double phi1= lat1*(pi/180);
double phi2 = lat2*(pi/180);
double delta1=(lat2-lat1)*(pi/180);
double delta2=(lng2-lng1)*(pi/180);
double cal1 = sin(delta1/2)*sin(delta1/2)+(cos(phi1)*cos(phi2)*sin(delta2/2)*sin(delta2/2));
double cal2= 2 * atan2((sqrt(cal1)), (sqrt(1-cal1)));
double distance =radEarth*cal2;
return (distance);
}
List distanceList; //list defination
// Call this function every time you want to calculate distance between currentLocation and all location in firestore.
void calculateDistanceAndStore(double currentLat, double currentLng) async{
distanceList=[];p;
final data = await Firestore.instance
.collection('collection_name')
.getDocuments();
doc.forEach((e){
double lat=e.data[ITEM_LATITUDE]; //asuming ITEM_LATITUDE is the field name in firestore doc.
double lng=e.data[ITEM_LONGITUDE];//asuming ITEM_LONGITUDE is the field name in firestore doc.
double distance = calculateDistance(currentLat,currentLng, lat,lng);
distanceList.add(distance);
});
}
Upvotes: 1