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,
});
Related
I am trying to update a field in the last document in the Firestore collection. My updating method is below:
updateHours() {
return usersRef.doc(firebaseAuth.currentUser!.uid).collection('posts')
.orderBy('datePublished', descending: true)
.limit(1).get().then((querySnapshot) {
return querySnapshot.docs.map((e) {
usersRef
.doc(firebaseAuth.currentUser!.uid).collection('posts')
.doc(e.reference.id)
.update({"totalTime": FieldValue.increment(1)});
});
});
}
This does not work. If I use .forEach(), then all documents get updated. So, how to update only the last document field?
To be able to update the totalTime field inside the last document, please use the following lines of code:
void updateHours() async{
CollectionReference postsRef = usersRef
.doc(firebaseAuth.currentUser!.uid)
.collection('posts');
QuerySnapshot query = await postsRef.orderBy('datePublished', descending: true)
.limit(1)
.getDocuments();
query.documents.forEach((doc) {
doc.reference.updateData({"totalTime": FieldValue.increment(1)});
});
}
Don't forget that Firebase APIs are asynchronous, and you need to wait for the data until it becomes available.
I need to just check if user email exist in user collection or not. Right now I am doing like this
var check = await FirebaseFirestore.instance.collection('users')
.where('email', isEqualTo: userData['email']).snapshots();
But when I print check its showing
Instance of '_MapStream<QuerySnapshotPlatform, QuerySnapshot<Map<String, dynamic>>>'
How can I check that email exist in that collection or not ? Thanks 😊
Your check variable is a QuerySnapshot object, while you seem to want it to be a boolean that indicates whether the query had any results.
To get that:
var query = FirebaseFirestore.instance.collection('users')
.where('email', isEqualTo: userData['email']).limit(1);
var snapshot = await query.snapshots();
var check = query.size > 0;
Note that I also added a limit(1) to the query, since you never need to read more than one document to determine if any matches exist.
you can try or take reference form this method let me know if this solves your problem
static Future<bool> emailCheck(String email) async {
bool result = false;
QuerySnapshot snapshot = await
FirebaseFirestore.instance.collection('Users').get();
snapshot.docs.forEach((f) {
if (f['email'] ==email) {
result =true;
}
});
return result;
}
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.
I have a function which i created to query firestore and checkwhether a phoneNo exist in the collection called 'users'
I want to get a boolean if the phone No already exist in a document field.
How can i do it
Future<bool> phoneRegisterCheck(phone) async{
bool phoneAlreadyRegistered;
print('start');
var result = Firebase.instance.collection('users').where('phoneNo', isEqualTo: phone);
}
I want to wait for the query to finish and then return the result.
When i Use listen method it is not happening right function returns null.
Future<bool> phoneRegisterCheck(phone) async{
bool phoneRegistered;
print('start');
var result = DatabaseService().userCollection.where('phoneNo', isEqualTo: phone);
result.snapshots().listen((val){
val.documents.isEmpty ? phoneRegistered=false:phoneRegistered=true;
});
return phoneRegistered;
}
Since streams are asynchronous then in your code the return statement will be executed before the data is fully retrieved therefore you get null. You should
use await for instead of listen:
Future<bool> phoneRegisterCheck(phone) async{
bool phoneRegistered;
print('start');
var result = DatabaseService().userCollection.where('phoneNo', isEqualTo: phone).snapshots();
await for(var values in result){
values.documents.isEmpty ? phoneRegistered=false:phoneRegistered=true;
}
return phoneRegistered;
}
From the docs:
Streams can be created in many ways, which is a topic for another article, but they can all be used in the same way: the asynchronous for loop (commonly just called await for) iterates over the events of a stream like the for loop iterates over an Iterable.
https://dart.dev/tutorials/language/streams
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