Firestore merge 2 queries with Flutter - firebase

I am trying to make 2 queries to Firestore and merge the results into one in order to simulate a Firestore OR query.
I segmented my code according to the bloc pattern.
///private method to zip QuerySnapshot streams
Stream<List<QuerySnapshot>> _combineStreams(String userId) {
var stream1 = todosCollection
.where("owners", arrayContains: userId)
.snapshots();
var stream2 = todosCollection
.where("contributors", arrayContains: userId)
.snapshots();
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
///exposed method to be consumed by repository
Stream<List<Todo>> todos(String userId) {
var controller = StreamController<List<Todo>>();
_combineStreams(userId).listen((snapshots) {
List<DocumentSnapshot> documents = List<DocumentSnapshot>();
snapshots.forEach((snapshot) {
documents.addAll(snapshot.documents);
});
final todos = documents.map((doc) {
return Todo.fromEntity(TodoEntity.fromSnapshot(doc));
}).toList();
controller.add(todos);
});
return controller.stream;
}
In my bloc I have the following code that should update my view accordingly my database state but it's not working. The database insertion work but the view doesn't refresh and I don't know why.
_gardensSubscription?.cancel();
_gardensSubscription = _dataRepository.gardens(event.userId).listen(
(gardens) {
dispatch(
GardensUpdated(gardens),
);
},
);
I am not very confortable with Stream and particularly with StreamController process. Is it possible to do this task more easily?

Time to use the great powers of RxDart: https://pub.dev/packages/rxdart
You can't do all types of streams transformations with this lib.
For example, you can use the merge operators to achieve exactly what you want

Related

Flutter Firestore Update Where

I'm trying to run a query that retrieves a single row given a where clause and updates it. I understand that Firebase doesn't support an UpdateWhere operations so I'm trying to use a Transaction instead.
I'm having difficulty making it work, maybe I'm too used to sql dbs... Here's my broken code
try {
final whereQuery = _db
.doc(userPath(user))
.collection("someInnerCollection")
.where("active", isEqualTo: true)
.limit(1);
await _db.runTransaction((transaction) async {
final entry = await transaction.get(whereQuery); // This doesn't compile as .get doesn't take in a query
await transaction.update(entry, {
"someValue": "newValue",
});
});
} catch (e) {
...
}
From the test I’ve made, I would suggest the following to achieve what you mention:
Based on the following answer:
As you can see from the API documentation, where() returns a Query object. It's not a DocumentReference.
Even if you think that a query will only return one document, you still have to write code to deal with the fact that it could return zero or more documents in a QuerySnapshot object. I suggest reviewing the documentation on queries to see examples.
After doing the query consult, you have to get the DocumentReference for that given result.
Then, you can use that reference to update the field inside a Batched writes
try {
final post = await firestore
.collection('someInnerCollection')
.where('active', isEqualTo: true)
.limit(1)
.get()
.then((QuerySnapshot snapshot) {
//Here we get the document reference and return to the post variable.
return snapshot.docs[0].reference;
});
var batch = firestore.batch();
//Updates the field value, using post as document reference
batch.update(post, { 'someValue': 'newValue' });
batch.commit();
} catch (e) {
print(e);
}
You are passing the DocumentSnapshot back in the update() operation instead of DocumentReference itself. Try refactoring the like this:
final docRefToUpdate = _db.collection("colName").doc("docId");
await _db.runTransaction((transaction) async {
final entry = await transaction.get() // <-- DocRef of document to update in get() here
await transaction.update(docRefToUpdate, {
// Pass the DocumentReference here ^^
"someValue": "newValue",
});
});
You can use a collection reference and then update single fields using .update().
final CollectionReference collectionReference = FirebaseFirestore.instance.collection('users');
await collectionReference.doc(user.uid).collection('yourNewCollection').doc('yourDocumentInsideNestedCollection').update({
'singleField': 'whatever you want,
});
Same code using "where"
collectionReference.doc(user.uid).collection('yourNewCollection').doc().where('singleField', isEqualTo: yourValue).update({
'singleField': 'whatever you want,
});

Dart firebase AND equivalent

My assumption was multiple where conditions make AND equivalent , as in
.collection("Property")
.where('FirebaseAuthId', isEqualTo: userId)
.where('PropertyId', isEqualTo: PropertId);
but on analysing the firestore DB , this seems to work more like an OR .how can the AND achieved for updating document .
Future updateLastChatVisitTime(userId, PropertId) async {
WriteBatch batch = Firestore.instance.batch();
var _documentRef = await Firestore.instance
.collection("Property")
.where('FirebaseAuthId', isEqualTo: userId)
.where('PropertyId', isEqualTo: PropertId);
_documentRef.getDocuments().then((ds) async {
if (ds != null) {
ds.documents.forEach((value) {
print('MAPPINGS are ${value.documentID}');
batch.updateData(
Firestore.instance
.collection("Property")
.document(value.documentID),
{"LastVisitTime": DateTime.now().toUtc()});
});
await batch.commit().then((value) {
print("Batch updateLastChatVisitTime update ok");
}).catchError((err) {
print('Error updateLastChatVisitTime update $err');
});
}
});
}
Also this link from firebase says it acts like Logical AND , am confused , did I miss something
https://firebase.google.com/docs/firestore/query-data/queries#compound_queries
.where() query can also act as filters - they will not generally include records where a field is missing. It's quite possible your records (which you do not show) are inconsistent; some may be missing one or the other of the fields. I'm not sure what the compound index would create under the circumstances.

How can I check the length of a firebase document using stream builder

In my flutter firebase app, I am able to get the length of a user's activity document in firebase using a query snapshot. However, I want the number of documents to update in real-time without the user needing to refresh the page. Can I do that by converting the codes below using stream builder to get the real-time length and how can I do that?
this is the code am using now which works perfectly well but doesn't update in real-time.
//this is the code I want to convert to stream
//builder.
static Future<int> numActivities(String userId)
async {
QuerySnapshot userActivitiesSnapshot = await
activitiesRef
.document(userId)
.collection('userActivities')
.where('seen', isEqualTo: '')
.getDocuments();
return userActivitiesSnapshot.documents.length;
}
You need to use the docs property, which "returns a List containing DocumentSnapshot classes", as follows:
return userActivitiesSnapshot.docs.length;
To get a stream of documents, you need to use the .snapshots() method which returns a Stream of QuerySnapshot instead of the .getDocuments() (deprecated in favor of .get()) method which returns a Future of QuerySnapshot.
Then you can map Stream<Snapshot> into a stream of the length of the snapshot's documents.
Your numActivities method should look like this:
static Stream<int> numActivities(String userId) {
return activitiesRef
.document(userId)
.collection('userActivities')
.where('seen', isEqualTo: '')
.snapshots()
.map((documentSnapshot) => documentSnapshot.docs.length);
}
Using this in your use case, you need to listen to the stream and update the _activityCount variable.
_setUpactivityCount() async {
final String currentUserId =
Provider.of<UserData>(context, listen: false).currentUserId;
DatabaseService.numActivities(currentUserId).listen((activityCount) {
if (mounted) {
setState(() {
_activityCount = activityCount;
});
}
});
}
Make sure you take care of _activityCount being null in it's initial state.

Compund Query with OR operator flutter and firebase [duplicate]

This question already has answers here:
How to perform compound queries with logical OR in Cloud Firestore?
(12 answers)
Closed 2 years ago.
I am creating a flutter app that should query a Firestore collection and return results if two conditions are met. Here is my code:
Stream<List<FormQuestions>> get question {
return someCollection
.where('myQUestion', isEqualTo: 'nameQuestion')
.snapshots()
.map(_someQuestionListFromSnapshot);
}
If I do with just one .where() condition, it works fine. But with two, it gives no results although I have documents that meet both conditions. I would like it to return for multiple .where() conditions like so:
Stream<List<FormQuestions>> get question {
return someCollection
.where('myQUestion', isEqualTo: 'nameQuestion')
.where('myQuestion', isEqualTo: 'ageQuestion')
.snapshots()
.map(_someQuestionListFromSnapshot);
}
Is there a way to add an OR(||) operator or how can I do this so that I get results for both "nameQuestion" and "ageQuestion"?
Kindly assist. Thanks.
Firestore does not support regular OR queries, but it does support IN queries with which you can implement what you need.
You query would look like citiesRef.where('country', 'in', ['USA', 'Japan']);
someCollection
.where('myQUestion', whereIn: ['nameQuestion', 'ageQuestion'])
Also see:
Firebase Firestore - OR query, which has answers that cover all kinds of variations of OR conditions.
the Flutter reference documentation for the where method
You can do the following:
With getDocuments():
void getData() async {
List<Future<QuerySnapshot>> futures = [];
var firstQuery = someCollection
.where('myQUestion', isEqualTo: 'nameQuestion')
.getDocuments();
var secondQuery = someCollection
.where('myQuestion', isEqualTo: 'ageQuestion')
.getDocuments();
futures.add(firstQuery);
futures.add(secondQuery);
List<QuerySnapshot> results = await Future.wait(futures);
results.forEach((res) {
res.documents.forEach((docResults) {
print(docResults.data);
});
});
}
So here you preform an OR query, and return the result from the two queries. Future.wait(futures) will wait until both of these queries are finished and return a `List that will contain the documents that satisfy these two queries.
With snapshots():
import 'package:async/async.dart' show StreamGroup;
///
getData() async {
List<Stream<QuerySnapshot>> streams = [];
final someCollection = Firestore.instance.collection("users");
var firstQuery = someCollection
.where('myQUestion', isEqualTo: 'nameQuestion')
.snapshots();
var secondQuery = someCollection
.where('myQuestion', isEqualTo: 'ageQuestion')
.snapshots();
streams.add(firstQuery);
streams.add(secondQuery);
Stream<QuerySnapshot> results = StreamGroup.merge(streams);
await for (var res in results) {
res.documents.forEach((docResults) {
print(docResults.data);
});
}
}
snapshots() returns a Stream which is a source of asynchronous data events, so it will keep listening for incoming data. To merge the two or query, you need to use StreamGroup.merge(streams) which merges the events from streams into a single single-subscription stream. Then using await for you can iterate over the events of a stream like the for loop iterates over an Iterable.
Check:
https://dart.dev/tutorials/language/streams
You cannot find a document using both where() calls because you are querying on the same myQUestion property. There is no way a property can hold two values at the same time. It can hold one or the other. You might be looking for an OR operator but using an AND in the way you do, it's not possible. An AND will only work when you query on different properties.

Merge Firestore's separate queries Streams in Dart

I'm implementing a Todo Application in Flutter. I need to merge a double query in client, in order to perform an OR request in Firestore.
One hand, I have the following code that performs the double queries.
Future<Stream> combineStreams() async {
Stream stream1 = todoCollection
.where("owners", arrayContains: userId)
.snapshots()
.map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
Stream stream2 = todoCollection
.where("contributors", arrayContains: userId)
.snapshots()
.map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
And other hand, I have the following code that will perform the update of view with the Bloc pattern.
Stream<TodosState> _mapLoadTodosToState(LoadTodos event) async* {
_todosSubscription?.cancel();
var res = await _todosRepository.todos(event.userId);
_todosSubscription = res.listen(
(todos) {
dispatch(
TodosUpdated(todos));
},
);
}
I have the following error.
flutter: Instance of '_AsBroadcastStream<List<List<Todo>>>'
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: type 'List<List<Todo>>' is not a subtype of type 'List<Todo>'
I tried to look for more infos with the debugger and it turned out that my StreamZip source contains the 2 stream separately.
For the moment I can get one stream at a time.
I don't know how to proceed in order to get the 2 streams and display them.
You're doing a map of a map, which returns a List of another List.
You should zip the QuerySnapshot streams and do the mapping after creating the subscription, and then you can create a new Stream<List<Todo>> from it.
///private method to zip QuerySnapshot streams
Stream<List<QuerySnapshot>> _combineStreams() async {
Stream stream1 = todoCollection
.where("owners", arrayContains: userId)
.snapshots()
});
Stream stream2 = todoCollection
.where("contributors", arrayContains: userId)
.snapshots()
});
return StreamZip(([stream1, stream2])).asBroadcastStream();
}
///exposed method to be consumed by repository
Stream<List<Todo>> todosStream() {
var controller = StreamController<List<Todo>>();
_combineStreams().listen((snapshots) {
List<DocumentSnapshot> documents;
snapshots.forEach((snapshot) {
documents.addAll(snapshot.documents);
});
final todos = documents.map((document) {
return Todo.fromEntity(TodoEntity.fromSnapshot(doc));
}).toList();
controller.add(todos);
});
return controller.stream;
}

Resources