How to compare multiple fields in a query from Firestore? - firebase

I'm trying to find a way to compare the location of all users and show nearby people as a result. In this question, Frank explains how it should be done. But I have no idea how to do the third step.
That's what I've achieved so far:
double _userLatitude;
double _userLongitude;
_getUserLocation() async {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
_userLatitude = position.latitude;
_userLongitude = position.longitude;
final firebaseUser = await FirebaseAuth.instance.currentUser();
if (firebaseUser != null)
await usersRef.document(firebaseUser.uid).updateData({
"location": "active",
"latitude": _userLatitude,
"longitude": _userLongitude,
});
}
_getNearbyUsers() {
usersRef.where('location', isEqualTo: "active").getDocuments();
Geolocator.distanceBetween(
_userLatitude, _userLongitude, endLatitude, endLongitude);
}
StreamBuilder(
stream: _getNearbyUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Text(
"Loading...",
),
);
} else if (snapshot.data.documents.length == 1) {
return Center(
child: Text(
":/",
),
);
}
return ListView.builder(
padding: EdgeInsets.only(top: 10),
scrollDirection: Axis.vertical,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_buildListUsers(context, snapshot.data.documents[index]));
},
),
Widget _buildListUsers(BuildContext context, DocumentSnapshot document) {
List<Users> usersList = [];
var data = document.data;
if (data["id"] != _userId) {
Users user = Users();
user.id = document["id"];
usersList.add(user);
return ListTile(
onTap: () {
Navigator.pushNamed(context, "/users", arguments: user);
},
title: Text(
document['id'],
),
);
}
return SizedBox();
}
I have no idea what to do next in _getNearbyUsers().

The Geolocator.distanceBetween function returns the distance in meters. With the distance you should apply the logic for your application, like performing an action if the distance is smaller than 500m, for example.
Something like this:
double distanceInMeters = Geolocator.distanceBetween(_userLatitude, _userLongitude, endLatitude, endLongitude);
if (distanceInMeters < 500) {
// Your custom logic goes here
}

Related

Is there anyway to sort incoming data from StreamBuilder?

Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance.collection('Listings').snapshots();
I am using this stream.
And i have streambuilder like this.
Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data = document.data()! as Map<String, dynamic>;
String start_location=data['start_location'];
String end_location=data['end_location'];
String date=data['date'];
String time=data['time'];
String price=data['price'];
String name_surname=data['name_surname'];
String userId=data['user_id'];
String coord=data['coord'];
var splitted=coord.split('/');
for(int i=0; i<splitted.length-1; i++){
String x = splitted[i];
var splitted2=x.split('-');
double result=calculateDistance(widget.place_latlng.latitude, widget.place_latlng.longitude, double.parse(splitted2[0]), double.parse(splitted2[1]));
if(result<1 && start_location!= widget.placename){
print("Found!");
return GestureDetector(
onTap: (){
//onTap func
},
child: buildTripCard(
context,
start_location: start_location,
end_location: end_location,
date: date,
time: time,
price: price,
name_surname: name_surname,
userId: userId,
),
);
}
}
return Container();
}).toList(),
);
},
),
)
I put into the incoming data to calculateDistance function. It returns double value (result).If that value less than 1, it shows in ListView. What i want to do is, sort Data which shows in listView order by result value.
How can i reach list that i created with .toList() method?
Sort data by chaining into stream. The below example takes all photos, which title starts with p. Converting Future to Stream is just for demonstration.
Stream<List<Photo>> photos() {
return Stream.fromFuture(
http.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'))
).map((r) => parsePhotos(r.body))
.map((list) => list.where((p) => p.title.startsWith('p')).toList());
}
Then use ListView.builder to render the final list.
StreamBuilder<List<Photo>>(
stream: photos(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(
child: Text('An error has occurred!'),
);
} else if (snapshot.hasData) {
final photos = snapshot.data!;
return ListView.builder(
itemCount: photos.length,
itemBuilder: (context, index) {
return Text(photos[index].title);
},
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
}

The operator '[]' isn't defined for the type 'Object' when trying to access data from futureBuilder

im trying to return two futures and getting the above error when trying to access the data within the returned snapshot.Specifically this line:
${snapshot.data!["firstName"]}
Any ideas as to the correct way to access this data, can't see any documentation anywhere defining this.
Code is as follows:
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([getData(), getMood()]),
builder: (_, snapshot) {
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(
title: const Text('Display the Picture'),
backgroundColor: kPrimaryColor,
),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 20.0, 8.0, 8.0),
child: Column(
children: [
Center(
child: Text(
"${snapshot.data!["firstName"]} \n\n "
"We have predicted your mood as:\n\n "
"${snapshot.data!.data()!["mood"]}\n\n"
"Please select a reason associated to your mood",
style: const TextStyle(
color: Colors.black, fontSize: 15),
textAlign: TextAlign.center,
),
),
],
),
),
],
),
),
);
} else {
return CircularProgressIndicator();
}
},
);
}
Future getData() async {
var currentUser = FirebaseAuth.instance.currentUser;
DocumentSnapshot q1 = await FirebaseFirestore.instance
.collection('USER_TABLE')
.doc(currentUser!.uid)
.get();
return q1;
}
Future getMood() async {
var currentUser = FirebaseAuth.instance.currentUser;
QuerySnapshot q2 = await FirebaseFirestore.instance
.collection('userMood')
.where('userId' == currentUser!.uid)
.orderBy('createdAt',descending: true)
.limit(1)
.get();
return q2;
}
}
Thanks
EDIT
future: Future.wait([getData(), getMood()]),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.hasData) {
var a = snapshot.data![0]["firstName"] as String;
var b = snapshot.data![1]['Prediction'] as String;
Future <String> getData() async {
var currentUser = FirebaseAuth.instance.currentUser;
DocumentSnapshot q1 = await FirebaseFirestore.instance
.collection('USER_TABLE')
.doc(currentUser!.uid)
.get();
return q1.toString();
}
Future <String> getMood() async {
var currentUser = FirebaseAuth.instance.currentUser;
QuerySnapshot q2 = await FirebaseFirestore.instance
.collection('userMood')
.where('userId' == currentUser!.uid)
.orderBy('createdAt',descending: true)
.limit(1)
.get();
return q2.toString();
}
Let's simplify the task:
Future getA() async {
await Future.delayed(Duration(seconds: 1));
return 12.34;
}
Future getB() async {
await Future.delayed(Duration(seconds: 1));
return 'abc';
}
... FutureBuilder(
future: Future.wait([getA(), getB()]),
builder: (context, snapshot) {
if (snapshot.hasData) {
// how to get a and b from snapshot?
} else {
return CircularProgressIndicator();
}
},
)
You're trying to do snapshot.data!['a'] and snapshot.data!['b']. It doesn't work because snapshot.data isn't Map<String, something>.
Future.wait returns Future<List<T>>. So snapshot.data will be List<T>, and you can get individual items like snapshot.data![0] and snapshot.data![1].
Another problem is that Future.wait doesn't support different item types, so the items type will be Object and you'll need to cast it manually.
Future<double> getA() async {
await Future.delayed(Duration(seconds: 1));
return 12.34;
}
Future<String> getB() async {
await Future.delayed(Duration(seconds: 1));
return 'abc';
}
... FutureBuilder<List<Object>>(
future: Future.wait([getA(), getB()]),
builder: (context, snapshot) {
if (snapshot.hasData) {
var a = snapshot.data![0] as double;
var b = snapshot.data![1] as String;
return Text('$a $b');
} else {
return CircularProgressIndicator();
}
},
)

How can I change a Futurebuilder into an Streambuilder?

I'm using a future builder in a method and trying to switch to a Streambuilder but struggling a it with that heres my code may be anyone can help
class _MeineFreundeState extends State<MeineFreunde> {
Icon custIcon = Icon(Icons.search);
Widget cusSearchBar = Text("Meine Freunde");
Stream myVideos;
int likes = 0;
int videos = 0;
int followers;
int following;
bool dataisthere = false;
#override
void initState() {
super.initState();
getalldata();
}
getalldata() async {
var listOfIds = [];
String myID = FirebaseAuth.instance.currentUser.uid;
var idofotheruser = await FirebaseFirestore.instance
.collection('meinprofilsettings')
.doc(myID)
.collection('following')
.get();
following = idofotheruser.docs.length;
idofotheruser.docs.forEach((element) {
listOfIds.add(element.id);
});
print(listOfIds);
myVideos = FirebaseFirestore.instance
.collection('videos')
.where('uid', isEqualTo: 'Fp3unLwcl2SGVh4MbUPiRVAylYV2')
.snapshots();
var documents = await FirebaseFirestore.instance
.collection('videos')
.where('uid', isEqualTo: 'Fp3unLwcl2SGVh4MbUPiRVAylYV2')
.get();
if (!mounted) return;
setState(() {
videos = documents.docs.length;
});
for (var item in documents.docs) {
likes = item.data()['likes'].length + likes;
}
var followersdocuments = await FirebaseFirestore.instance
.collection("meinprofilsettings")
.doc(myID)
.collection('followers')
.get();
var followingdocuments = await FirebaseFirestore.instance
.collection("meinprofilsettings")
.doc(myID)
.collection('following')
.get();
followers = followersdocuments.docs.length;
following = followingdocuments.docs.length;
setState(() {
dataisthere = true;
});
}
#override
Widget build(BuildContext context) {
return getBody(context);
}
Widget getBody(BuildContext context) {
return dataisthere == false
? Scaffold(body: Center(child: CircularProgressIndicator()))
: Stack(children: <Widget>[
Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
Navigator.of(context)
.pushNamed(Searchuserinmeinebeitraege.route);
},
),
],
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: RefreshIndicator(
onRefresh: _handleRefresh,
color: Colors.black,
strokeWidth: 4,
child: ListView(
children: [
Column(children: <Widget>[
SizedBox(
height: 5,
),
StreamBuilder(
stream: myVideos,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (videos > 0) {
return StaggeredGridView.countBuilder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: ScrollPhysics(),
crossAxisCount: 3,
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot video =
snapshot.data.docs[index];
return InkWell(
onTap: () {
NavigationService.instance
.navigateToRoute(MaterialPageRoute(
builder: (context) {
return VideoPage(
video.data()['videourl'],
video.data()['uid'],
video.id,
);
}));
},
child: Card(
elevation: 0.0,
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
clipBehavior:
Clip.antiAliasWithSaveLayer,
child: Image.network(
video.data()['previewimage'],
fit: BoxFit.cover,
),
),
//imageData: searchImages[index],
),
);
},
staggeredTileBuilder: (index) =>
StaggeredTile.count(
(index % 7 == 0) ? 2 : 1,
(index % 7 == 0) ? 2 : 1),
mainAxisSpacing: 8.0,
crossAxisSpacing: 4.0,
);
} else {
return Center(
child: Padding(
padding:
const EdgeInsets.fromLTRB(0, 100, 0, 0),
child: Container(
child: Text(
"No Videos Yet",
style: TextStyle(
fontSize: 18, color: Colors.black),
),
),
),
);
}
}),
]),
],
),
),
),
]);
}
Future _handleRefresh() async {
await Future.delayed(new Duration(seconds: 2));
setState(() {
getalldata();
});
return null;
}
}
I am a beginner with flutter, I know that I just can change FuturBuilder into Streambuilder and then future to stream but what about How I'm getting the data is there any difference
I Mean something like this line
video.data()['videourl'],
Is it equal or is there any difference and also how can I change it in getalldata method. If you need more information please leave a comment.
StreamBuilder is different from FutureBuilder in many ways one main difference being
The main job of the FutureBuilder is to complete the future and return the result once the result is returned it has no way to fetch the latest snapshot from the future unless its parent rebuilds. Once the future attached returns the result the builder method gets executed to refresh the Ui.
while incase of StreamBuilder it contiuously listens to your specified collection and gets you the latest snapshot in realtime. that means any document in your firestore collection changes you get the latest updated collection and builder method rebuilds to refresh the UI.
You could use StreamBuilder to fetch data from your firestore's collection like this
String myID = FirebaseAuth.instance.currentUser.uid;
final queryVideos = await FirebaseFirestore.instance
.collection('videos')
.where('uid', arrayContains: listOfIds)
StreamBuilder<DocumentSnapshot>(
stream: queryVideos.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator()); /// show a loader
} else if (snapshot.data.docs.isEmpty) {
return const SizedBox.shrink(); // show an empty widget no docs
} else
return StaggeredGridView.countBuilder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
physics: ScrollPhysics(),
crossAxisCount: 3,
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
/// fetch a doc by Index
final doc = snapshot.data.docs[index];
return InkWell(
onTap: () {
NavigationService.instance
.navigateToRoute(MaterialPageRoute(
builder: (context)=>VideoPage(
doc['videourl'], // this is how you access each property in a document
doc['uid'],
doc['id']
));
},
child: YourWidget());
}));
});

Async Functions: Future<Query Snapshot> to Bool in Flutter?

I have this piece of code right here: if the user exists in the database and it's followed by the CurrentUser it builds an "Unfollow" custom button, otherwise it builds a follow button
CustomButton follow = CustomButton("Follow", Colors.red, Colors.red, Colors.white, user);
CustomButton unfollow = CustomButton("Unfollow", Colors.black, Colors.black, Colors.white, user);
AvatarHeader(user.username, " ", user.photoURL, checkIfFollowing(user.id)== true ? unfollow : follow)
the checkIfFollowing() function is always evaluated false probably because I have a problem in this asyncronus function which does not return a boolean value
checkIfFollowing(String fetchedUser) async{
DocumentSnapshot doc = await FOLLOWERS
.document(fetchedUser)
.collection('userFollowers')
.document(CURRENTUSER.id)
.get()
return doc.exists;
}
How can i fix this?
EDIT
search(String query) {
Future<QuerySnapshot> users = USERS
.where("username", isGreaterThanOrEqualTo: query)
.getDocuments();
print(query);
print("");
onStringChange(users);
}
FutureBuilder buildResults(){
return FutureBuilder (
future: results,
builder: (context, snapshot) {
if (!snapshot.hasData) {
print("i dont have data");
return circularProgress();
}
List<AvatarHeader> searchResults = [];
snapshot.data.documents.forEach((doc) async {
User user = User.fromDocument(doc);
if (user.photoURL != null) {
print(user.username);
bool check = await checkIfFollowing(user.id);
CustomButton follow = CustomButton("Follow", Colors.red, Colors.red, Colors.white, user);
CustomButton unfollow = CustomButton("Unfollow", Colors.black, Colors.black, Colors.white, user);
searchResults.add(AvatarHeader(user.username, " ", user.photoURL, check == true ? unfollow : follow));
}
});
return ListView(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
children: searchResults,
);
},
);
}
You need to return a Future in async functions, in your particular case you need a Future<bool>
Future<bool> checkIfFollowing(String fetchedUser) async{
DocumentSnapshot doc = await FOLLOWERS
.document(fetchedUser)
.collection('userFollowers')
.document(CURRENTUSER.id)
.get()
return doc.exists;
}
I recommend you to take a look to Asynchronous programming: futures, async, await
A future (lower case “f”) is an instance of the Future (capitalized “F”) class. A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
EDIT
Without more details is very hard to be sure how to solve your specific issue. If I understand correctly, you need to have something in line with this
FutureBuilder buildResults() {
return FutureBuilder(
future: results,
builder: (context, snapshot) {
if (!snapshot.hasData) {
print("i dont have data");
return circularProgress();
}
// get documents where user.photoURL != null
var docsWhereUserPhotoIsNotNull = getDocumentsWithUserPhotoNotNull(snapshot.data.documents);
// build a list of FutureBuilders
return ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: docsWhereUserPhotoIsNotNull.length,
itemBuilder: (context, index) {
var doc = docsWhereUserPhotoIsNotNull[index];
var user = User.fromDocument(doc);
return FutureBuilder(
future: checkIfFollowing(user.id),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return circularProgress();
}
bool check = snapshot.data;
CustomButton follow = CustomButton("Follow", Colors.red, Colors.red, Colors.white, user);
CustomButton unfollow = CustomButton(
"Unfollow", Colors.black, Colors.black, Colors.white, user);
return AvatarHeader(user.username, " ", user.photoURL, check == true ? unfollow : follow);
});
});
}
);
}
Where
List<DocumentSnapshot> getDocumentsWithUserPhotoNotNull(List<DocumentSnapshot> documents) {
var documentsWithUserPhotoNotNull = List<DocumentSnapshot>();
documents.forEach((doc) async {
User user = User.fromDocument(doc);
if (user.photoURL != null) {
documentsWithUserPhotoNotNull.add(doc);
}
});
return documentsWithUserPhotoNotNull;
}

how to get data from firebase in flutter

I am building a flutter app and using cloud-firestore,
this is how my database looks like
I want a function that retrieves all documents in the collection called "Driver List" in an array of strings
that what I had already used but it gets them back in a listview in a new screen
class DriverList extends StatelessWidget {#overrideWidget build(BuildContext context) {
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('DriverList').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['name']),
subtitle: new Text(document['phone']),
);
}).toList(),
);
},
);
}
}
This has some additional logic to remove potentially duplicate records, but you can use something like the following to retrieve data from Firestore.
We get access to a collection reference, then list the results of the query, then create local model objects for the data returned by Firestore, and then we return the a list of those model objects.
static Future<List<AustinFeedsMeEvent>> _getEventsFromFirestore() async {
CollectionReference ref = Firestore.instance.collection('events');
QuerySnapshot eventsQuery = await ref
.where("time", isGreaterThan: new DateTime.now().millisecondsSinceEpoch)
.where("food", isEqualTo: true)
.getDocuments();
HashMap<String, AustinFeedsMeEvent> eventsHashMap = new HashMap<String, AustinFeedsMeEvent>();
eventsQuery.documents.forEach((document) {
eventsHashMap.putIfAbsent(document['id'], () => new AustinFeedsMeEvent(
name: document['name'],
time: document['time'],
description: document['description'],
url: document['event_url'],
photoUrl: _getEventPhotoUrl(document['group']),
latLng: _getLatLng(document)));
});
return eventsHashMap.values.toList();
}
Source: https://github.com/dazza5000/austin-feeds-me-flutter/blob/master/lib/data/events_repository.dart#L33
Getting one time data:
var collection = FirebaseFirestore.instance.collection('DriverList');
var querySnapshot = await collection.get();
for (var queryDocumentSnapshot in querySnapshot.docs) {
Map<String, dynamic> data = queryDocumentSnapshot.data();
var name = data['name'];
var phone = data['phone'];
}
Getting data each time it changes, using a StreamBuilder:
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance.collection('DriverList').snapshots(),
builder: (_, snapshot) {
if (snapshot.hasError) return Text('Error = ${snapshot.error}');
if (snapshot.hasData) {
final docs = snapshot.data!.docs;
return ListView.builder(
itemCount: docs.length,
itemBuilder: (_, i) {
final data = docs[i].data();
return ListTile(
title: Text(data['name']),
subtitle: Text(data['phone']),
);
},
);
}
return Center(child: CircularProgressIndicator());
},
)
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class LoadDataFromFirestore extends StatefulWidget {
#override
_LoadDataFromFirestoreState createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
#override
void initState() {
super.initState();
getDriversList().then((results) {
setState(() {
querySnapshot = results;
});
});
}
QuerySnapshot querySnapshot;
#override
Widget build(BuildContext context) {
return Scaffold(
body: _showDrivers(),
);
}
//build widget as prefered
//i'll be using a listview.builder
Widget _showDrivers() {
//check if querysnapshot is null
if (querySnapshot != null) {
return ListView.builder(
primary: false,
itemCount: querySnapshot.documents.length,
padding: EdgeInsets.all(12),
itemBuilder: (context, i) {
return Column(
children: <Widget>[
//load data into widgets
Text("${querySnapshot.documents[i].data['activation']}"),
Text("${querySnapshot.documents[i].data['car1']}"),
Text("${querySnapshot.documents[i].data['car2']}"),
Text("${querySnapshot.documents[i].data['car5']}"),
Text("${querySnapshot.documents[i].data['name']}"),
Text("${querySnapshot.documents[i].data['phone']}"),
],
);
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
//get firestore instance
getDriversList() async {
return await Firestore.instance.collection('DriversList').getDocuments();
}
}
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder(
stream:
FirebaseFirestore.instance.collection('messages').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
}
if (snapshot.hasData) {
final messages = snapshot.data!.docs;
List<Text> messageWigdets = [];
for (var message in messages) {
final messageText = message['text'];
final messageSender = message['sender'];
final messageWigdet =
Text('$messageText from $messageSender');
messageWigdets.add(messageWigdet);
}
return Expanded(
child: ListView(
children: [...messageWigdets],
),
);
}
return const CircularProgressIndicator.adaptive();
},
),

Resources