how to get document by Id from collectionGroup - firebase

my database look like this
users(collection)
user_id(document)
images(collection)
image_id(document)
i used collectionGroup to reach all images collection and now i want get the image by id.
Firestore.instance.collectionGroup('images').document('$imageid').get().then((onValue){
but with collectionGroup() there's no document()

In Swift language, I would go like this:
// (tested and confirmed)
Firestore.firestore().collectionGroup("users").getDocuments { (doc, err) in
if err != nil {
print(err?.localizedDescription)
}
if doc?.isEmpty == false {
for doc in doc!.documents {
print("DOC = ", doc.documentID)
}
}
}
I don't use Dart, but I found this online for you. You might have to tweak it so it compiles for you. I know it's using collection instead of collectionGroup.
final collRef = Firestore.instance.collection('images');
DocumentReferance docReferance = collRef.document();
docReferance.setData(map).then((doc) {
print('hop ${docReferance.documentID}');
}).catchError((error) {
print(error);
});
]

Related

Kotlin firebase executed with addOnSuccessListener even if fails

I have this piece of code and it is executed with .continueWith and addOnSuccessListener even if failed.I try with continueWithTask but i dont understand very well Tasks API. Please help me to understand how to do this.
db.collection(customGameName).document().get().continueWith {
if (!gameslist.contains(customGameName)) {
gameslist.add(customGameName)
SavedPreference.setGamesList(this, gameslist)
adapter.notifyDataSetChanged()
}else{
Toast.makeText(this,"$customGameName is in the list already!",Toast.LENGTH_SHORT).show()
}
}.addOnFailureListener {
Toast.makeText(this,"$customGameName not exist!",Toast.LENGTH_SHORT).show()
gameslist.remove(customGameName)
SavedPreference.setGamesList(this, gameslist)
adapter.notifyDataSetChanged()
}
If you have code that you only want to run when the task is successful, add a success listener to the task. That should look something like this:
db.collection(customGameName).document().get()
.addOnSuccessListener { document ->
if (document != null) {
Log.d(TAG, "DocumentSnapshot data: ${document.data}")
if (!gameslist.contains(customGameName)) {
gameslist.add(customGameName)
SavedPreference.setGamesList(this, gameslist)
adapter.notifyDataSetChanged()
}else{
Toast.makeText(this,"$customGameName is in the list already!",Toast.LENGTH_SHORT).show()
}
} else {
Log.d(TAG, "No such document")
}
}.addOnFailureListener {
Toast.makeText(this,"$customGameName not exist!",Toast.LENGTH_SHORT).show()
gameslist.remove(customGameName)
SavedPreference.setGamesList(this, gameslist)
adapter.notifyDataSetChanged()
}
Note that this code is pretty much straight from the Firebase documentation on getting a document in Kotlin, so I recommend spending some more time there.

How do I map my ViewModel's ID to the Document ID in Firestore?

I have the fetch Data code here, but I don't understand how I am supposed to delete documents without setting the ID to the Document's ID. I was following this tutorial here. https://medium.com/swift-productions/swiftui-easy-to-do-list-with-firebase-2637c878cf1a I'm assuming I need to do so in the data mapping but I don't understand how with this code. I want to remove a todo from a SwiftUI list and also delete it's entire Firestore Document.
func fetchData() {
db.collection("todos").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.todos = documents.map { (QueryDocumentSnapshot) -> Todo in
let data = QueryDocumentSnapshot.data()
let todoDetails = data["todo"] as? String ?? ""
return Todo(todoDetais: todoDetails)
}
}
}
View Model
struct Todo: Codable, Identifiable {
var id: String = UUID().uuidString
var todoDetais: String?
}
I recommend using Codable to map your Firestore documents to Swift structs. This will make your code easier to write, less prone to errors, and more type-safe.
Specifically, it will also enable you to use #DocumentID to map the Firestore document ID to the id attribute of your Swift struct.
Here's a quick example:
struct Book: Codable {
#DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
}
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.book = try document.data(as: Book.self)
}
catch {
print(error)
}
}
}
}
}
For more details, see this comprehensive guide I wrote about mapping Firestore documents to Swift structs (and back).
For more information about how to delete a Firestore document from a SwiftUI app, check out this article

SwiftUI + Firebase - Listener not listening for changes?

I've set up a listener, but it doesn't seem to be changing according to changes in the data. The flow is the following:
If userCustomHabit is empty, user sees a button
When clicked, user can enter text in a TextField from a sheet to add to userCustomHabit (an array of strings)
Now that userCustomHabit is not empty, they should see something else
However, the problem I'm seeing is that userCustomHabits isn't updating in the view itself even though it is updating in the Firestore database.
Anyone know why this is? Included code below:
View
#ObservedObject var viewModel = RoutinesViewModel()
Group {
if self.viewModel.userCustomHabits.isEmpty {
Button(action: {
self.showCreateSheet.toggle()
}) {
Text("Create your own habits")
.font(Font.custom("Roboto-Regular", size: 20))
.frame(width: geometry.size.width * 88/100, height: 200)
.foregroundColor(.black)
.background(Color.init(UIColor.systemGray5))
.cornerRadius(40)
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(style: StrokeStyle(lineWidth: 2, dash: [20]))
.foregroundColor(Color.init(UIColor.systemGray3))
)
}
}
else {
// Something else
}
}
.onAppear(perform: self.viewModel.newHabitsListener)
Sheet
VStack {
TextField("Enter text", text: $enteredText)
Button("Add Habit") {
self.viewModel.createNewHabits(newHabit: self.enteredText)
}
}
View Model
#Published var userCustomHabits = [String]()
func newHabitsListener() {
db.collection("users").document(currUser?.uid ?? "").addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
DispatchQueue.main.async {
self.userCustomHabits = data["userCustomHabits"] as! [String]
}
}
}
func createNewHabits(newHabit: String) {
db.collection("users").document(currUser?.uid ?? "").updateData(["userCustomHabits": FieldValue.arrayUnion([newHabit])])
}
So I played around with your code a bit (since the code sample is incomplete, I had to make a few assumptions), and it seems like you might never have created the document you're writing to in the first place.
updateData only updates existing documents (see the documentation). To create a new document, use setData (see the documentation)
When changing your code form updateData to setData, the listener kicked in as expected.
However, it might be better to add a sub-collection customHabits to each user document. This way, adding new habits is as simple as adding a new document, which also makes querying a lot easier.

SwiftUI Firebase - How to query a document then update?

Trying to query a document and then update it in a function in my ViewModel. Trying something like the below, but it doesn't work. Any advice would be appreciated!
func addToFruits(name: String) {
db.collection("fruits").whereField("name", isEqualTo: name)
.getDocument()
.limit(to: 1)
.updateData(["fruits": FieldValue.arrayUnion([name])])
}
func addToRoutine(routine: String, habit: String) {
db.collection("routines").whereField("name", isEqualTo: routine).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
document.updateData(["habits": FieldValue.arrayUnion([habit])])
}
}
}
}
In the first one, error I get is "Value of type 'Query' has no member 'getDocument'" and not sure how to resolve this. Second one, error I get is "Value of type 'QueryDocumentSnapshot' has no member 'updateData'"
It's not exactly clear what you're attempting to update but here's some quick example code that will read in a user named 'Steve' and update his age to 50. Keep in mind this will read in the FIRST user named 'Steve' and update their age - then when it's run again, will read the NEXT Steve etc etc - that may be what your attempting to do.
func readUsersAndUpdateTheirAgeTo50() {
let users = self.db.collection("users") //self.db points to *my* firestore
users.whereField("name", isEqualTo: "Steve").limit(to: 1).getDocuments(completion: { querySnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = querySnapshot?.documents else { return }
for doc in docs {
let docId = doc.documentID
let name = doc.get("name")
print(docId, name)
let ref = doc.reference
ref.updateData(["age": 20])
}
})
}
If I just wanted to update all Steve's age to 50, remove the limit
.limit(to: 1)
Note this code is kind of sloppy as since there is a limit of 1, we wouldn't need the loop. Also note that not every Steve is 50 so there should be additional parameters to narrow down which Steve it is - like a uid for example.

Firestore transactions with security rules making reads

I want to create two documents
Account/{uid} {
consumerId: ... //client generated id
}
Consumer/{consumerId} {
...
}
and I have a security rule for the consumer collection
match /Consumer/{consumerId} {
allow create: if (consumerId == get(/databases/$(database)/documents/Account/$(request.auth.uid)).data['consumerId'];
}
I need to ensure that an account can only add a consumer document with a consumerId corresponding to the one in their Account document. Both documents should be created together. I've been trying to do this with transactions but I keep getting the error "Transaction failed all retries.". Whats going wrong and how do I fix it?
The data variable is an object and not an array, so you should use data.consumerId instead of data['consumerId']:
match /Consumer/{consumerId} {
allow create: if consumerId == get(/databases/$(database)/documents/Account/$(request.auth.uid)).data.consumerId;
}
I ended up accomplishing this with a batch write and security rules.
match /consumer/{cid} {
function isNewResource() { return resource == null; }
allow create: if isRegistered();
allow read, update: if isNewResource();
}
And then client side with something along the lines of
createThing() {
const db = firebase.firestore();
const { uid, displayName } = this.auth.currentUser;
const batch = this.db.batch();
// Essentially generating a uuid
const newConsumerRef = db.collection("consumer").doc();
// Update the user doc
batch.update(
db.collection('/users').doc(uid),
{ consumerID: newConsuemrRef.id }
);
// Update the admins field in the new doc
batch.set(newConsumerRef, {
admins: {
[uid]: displayName,
},
});
return batch.commit();
}
My problem was the same, but the write to the field in the collections actually needed to be to an object key, so it looked a little funkier
batch.update(
db.collection('/users').doc(uid),
{ [`adminOf.${newRef.id}`]: 'some special name' }
);

Resources