flutter local notification on firestore change - firebase

In my firestore I have a list of documents which represent locations on a map.
I would like to show a local notification not only when a new document is created in the database, but also when the location is within a certain distance from my current location.
At the moment I have a streambuilder which loads the position into my local map, and a streamlistener to give a notification on a new document:
CollectionReference loc =
FirebaseFirestore.instance.collection('locations');
late Stream<QuerySnapshot> _locStream;
late StreamSubscription<QuerySnapshot> streamSub;
#override
void initState() {
super.initState();
_locStream = loc.snapshots();
streamSub = _locStream.listen((data) {
final snackBar = SnackBar(
content:
Text('New location added!'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
});
}
the problem is that the stream is returning ALL the documents, not only the new one, so I have no idea how to "isolate" the new one and, more important, how to get its value in order to compare it with my current location.
Is that possible to achieve so?

A Firestore QuerySnapshot always contains all information that fits within the query. But when the QuerySnapshot is an update on an existing listener/stream, it also contains metadata about what changes compared to the previous QuerySnapshot on the listener/stream.
To get access to this metadata, use the QuerySnapshot's docChanges property, rather than docs, and check the DocumentChangeType of the type property of each change to find only the documents that were added. In the initial snapshot that will be all of the documents, since all of them are new to the snapshot at that point.
See the Firebase documentation on viewing changes between snapshots

Related

Realtime data from firestore flutter not working

I was following this tutorial on how to get realtime updates from flutter firestore, https://medium.com/firebase-tips-tricks/how-to-use-cloud-firestore-in-flutter-9ea80593ca40 and I scrolled down to Listen For Realtime Updates section and when I followed the tutorial, this is what I came up with,
String name = 'name here';
String children = 'children here';
String docId = '0';
#override
void initState() {
getUsers();
super.initState();
}
getUsers() async {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final uid = user!.uid;
FirebaseFirestore.instance
.collection("userNames")
.where("uid", isEqualTo: uid)
.snapshots()
.listen((result) {
result.docs.forEach((result) {
print(result["firstName"]);
print(result["children"].toString());
name = result["firstName"];
children = result["children"].toString();
});
});
}
When I print the values to the console they update in realtime, but when I put them in variables and concatenate them into a Text widget like this:
Text('Children: $children'), //It does not update in realtime.
For instance, if in my document if I have children: 3 and I display in in my app, it shows 3, but when I manually change it, it does not update in realtime, I have to press hot reload. Another issue is that I have to initialize the variable before using them in the function, up ahead in the first 3 lines of code. When I hot restart, it shows the values of what I use to initialize them. For where it should show children, it says 'children here' and for where the name is, it puts 'name here', only when I hot reload the page, do the actual firestore values get inputed into them and show data from the firestore database. If there is a solution to any of these problems, I would much prefer an answer in code instead of a link or a brief explanation, I spend hours before I find a piece of code that utilizes the explanation. Thank you
I use snapshots().listen() to listen to change. Then I use ValueNotifier to notify the UI.
final itemsNotifier = ValueNotifier<List<Item>>([]);
FirebaseFirestore.instance
.collection("userNames")
.where("uid", isEqualTo: uid)
.snapshots()
.listen((event) {
itemsNotifier.value = event.docs
.map((doc) => Item.fromSnapshot(
doc as DocumentSnapshot<Map<String, dynamic>>))
.toList();
itemsNotifier.notifyListeners();
});
Since the data is loaded asynchronously, the data isn't available when Flutter first paints your Text widget. You'll need to tell Flutter that it has to repaint the UI when the data is available.
There are two common ways to do this:
Put the children variable in the state of your widget by calling setState(). This will tell Flutter to repaint the widget, and your text will then show the value.
You can also use a StreamBuilder widget, which does the above too - but it also handles all kinds of error states automatically.
I recommend reading about stateful widgets and setState and about the StreamBuilder class to learn more.

Firestore: Invalid document reference. Document references must have an even number of segments,

I have a futter app and want to add items favorite by the user in a separate collection "userFavorites" which will store all favorite items depending on the current user uid by doing so:
Future getCurrentUser() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
final uid = user.uid;
return uid.toString();
}
Future<void> toggleFavoriteStatus() async{
var userId = await getCurrentUser();
final oldStatus = isFavorite;
isFavorite = !isFavorite;
notifyListeners();
try{
await Firestore.instance.collection("userFavorites/$userId").document(id).updateData({
'isFavorite': isFavorite,
});
}catch(error){
_setFavValue(oldStatus);
}
}
But I receive this error when I try to favorite any item:
Invalid document reference. Document references must have an even number of segments, but userFavorites/FRbYxmNpSBcda6XOrQUjukvFvVb2/q7eLxtZfhG3g6Pd1bYY4 has 3
E/MethodChannel#plugins.flutter.io/cloud_firestore(14551): at com.google.firebase.firestore.DocumentReference.forPath(com.google.firebase:firebase-firestore##21.3.0:80)
The error message:
Invalid document reference. Document references must have an even number of segments, but userFavorites/FRbYxmNpSBcda6XOrQUjukvFvVb2/q7eLxtZfhG3g6Pd1bYY4 has 3
is telling you that you built a path to a document:
userFavorites/FRbYxmNpSBcda6XOrQUjukvFvVb2/q7eLxtZfhG3g6Pd1bYY4
which doesn't look like a document at all, since it has three path segments:
userFavorites
FRbYxmNpSBcda6XOrQUjukvFvVb2
q7eLxtZfhG3g6Pd1bYY4
This is the line of code that built your path:
Firestore.instance.collection("userFavorites/$userId").document(id)
Since we can't see your data, it's hard to tell what you actually meant to do here. But in any event, Firestore is taking "userFavorites" to be the name of a top level collection, "FRbYxmNpSBcda6XOrQUjukvFvVb2" is the name of a document in that collection, and "q7eLxtZfhG3g6Pd1bYY4" is taken to mean a subcollection under that document. If you meant something else, you'll have to figure out how to build the path to that document to query it.
I ran into this problem using a doc reference & instead of using [document_reference].path I accessed it using the ID: [document_reference].id
and that solved it for me.

Firebase Firstore subcollection

please how can I get all the value of my IndividualTaxData subcollection in Flutter.
First, you must get the reference to the parent document:
DocumentReference parentRef = Firestore.intances.collection('TaxData').document(taxDataId);
You can do the previous part with a direct reference to the document (like the code above) or with a query. Later, you must get the reference of the subcollection and the document that you get the information:
DocumentReference subRef = parentRef.collection('IndividualTaxData').document(individualTaxId);
And finally, get the data:
DocumentSnapshot docSnap = await subRef.get();
For you to return a simple document, you can use the following code for it.
var document = await Firestore.instance.collection('IndividualTaxData').document('<document_name>');
document.get() => then(function(document) {
print(document('character'));
// you can print other fields from your document
}
With the above code, you will reference your collection IndividualTaxData and then load it's data to a variable that you can print the values.
In case you want to retrieve all the documents from your collection, you can start using the below code.
final QuerySnapshot result = await Firestore.instance.collection('IndividualTaxData').getDocuments();
final List<DocumentSnapshot> documents = result.documents;
documents.forEach((data) => print(data));
// This print is just an example of it.
With this, you will load all your documents into a list that you iterate and print after - or that you can use with another method.
In addition to that, as future references, I would recommend you to check the following links as well.
Query a single document from Firestore in Flutter (cloud_firestore Plugin)
How to use Cloud Firestore with Flutter
Le me know if the information helped you!

Reducing the number of reads firestore

I have a dating kind of app (without chat support) in which I am showing list of all the profiles that matches certain criteria to user. I have used realtime snapshot listener for this.
query.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshot,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
return;
}
if (snapshot != null && !snapshot.isEmpty()) {
List<FeedDetails> feedDetailsList = new ArrayList<>();
for (DocumentSnapshot document : snapshot.getDocuments()) {
FeedDetails feedDetails = document.toObject(FeedDetails.class);
feedDetailsList.add(feedDetails);
}
feedItemAdapter.updateData(feedDetailsList);
}
}
});
In individual profile doc, I have 10-12 field with one online/offline info field. So lets say if no other field changed but online/offline status changed for some profiles then listener is going to read that document again. Is there any way to cut down those reads?
I have a 10-12 field with one online/offline info field. So let's say if no other field changed but online/offline status changed for some profiles then the listener is going to read that document again.
There is no way in Cloud Firestore in which you can listen only to a set of properties of a document and exclude others. It's the entire document or nothing. So if this field online=true is changed into online=false, then you will get the entire document.
Cloud Firestore listeners fire on the document level and always return complete documents. Unfortunately, there is no way to request only a part of the document with the client-side SDK, although this option does exist in the server-side SDK's select() method.
If you don't want to get notified for specific fields, consider adding an extra collection with documents that will contain only those fields. So create that additional collection where each document just contains the data you don't need. In this way, you won't be notified for online/offline changes.

Async loading two dependent streams when using FutureBuilder

In a pretty complex sheet, I am using FutureBuilder and Listview.builder which help me to list all groups around me (using Algolia search), which looks as follows:
future = _algolia
.index('groups')
.setAroundLatLng(
'${_startLocation.latitude.toString()}, ${_startLocation.longitude.toString()}')
.setFacetFilter("category:$category")
.search(_query)
.setHitsPerPage(1000)
.getObjects();
Now, I also want to include the totalUsers for each group. Problem: they are saved in Firestore (groups/groupId/totalUsers). Now for Firestore when I need to request the totalUsers, I have to hand over every groupId (which comes async from Algolia and is simply available in FutureBuilder as document.data['groupId']). So I tried to call totalUsers from a ListTile within FutureBuilder, with getTotalUsers(document.data['groupId']), and
getTotalUsers(groupId) async{
DocumentReference reference =
Firestore.instance.collection('groups').document(groupId.toString());
DocumentSnapshot snapshot;
snapshot = await reference.get();
return snapshot['totalUsers'];
}
...but since it is async, it just gives me 'Instance of a Future'. Now what's the easiest way to include totalUsers and 'await' it from the widget tree? I think I cannot load it in initstate, since I first have to hand over the specific userIds from the groups 'aroundme'.

Resources