Is there a way to create Auth object and use that UID to create a doc with GeoFirestore - firebase

I am trying to create an Auth object in firebase that returns the User UID. I want to be able to create a document in my collection with that particuar UID but apparently geofirestore doesn't have a feature to add a document with a particular ID.
const storesCollection = geoFirestore.collection("retailers");
export const firstTimeStartCreateRetailer = ( email, password) => async dispatch => {
try {
const { user } = await auth.createUserWithEmailAndPassword(email, password);
await storesCollection.doc(user.uid).add({
coordinates: new firebase.firestore.GeoPoint(33.704381, 72.978839),
name: 'Freshlee',
location: 'F-11',
city: 'Islamabad',
inventory: [],
rating: 5,
categories: []
})
dispatch({ type: LOGIN, payload: { ...user } });
} catch (error) {
console.log(error)
}
};
this code is rejected because geoFirestore doesn't have the .doc(id) referencing feature. How can I achieve this.

You need to do
await storesCollection.doc(user.uid).set({...})
using the set() method. As a matter of fact, there is no add() method for a GeoDocumentReference and storesCollection.doc(user.uid) is a GeoDocumentReference.
The add() method is a method of a GeoCollectionReference.

Because storesCollection is a GeoCollectionReference, the API is not always the same as native Firestore references.
In your particular case, you get the document you want to write to using doc(id), but instead of using add(...) which is used on collections, you need to use set(...) instead to create/overwrite the data for that particular document.
await storesCollection.doc(user.uid).set({
coordinates: new firebase.firestore.GeoPoint(33.704381, 72.978839),
name: 'Freshlee',
location: 'F-11',
city: 'Islamabad',
inventory: [],
rating: 5,
categories: []
});

Related

Firebase: using DocumentSnapshot id as User uid. Thoughts?

Currently users profiles are created by adding a user to a firestore collection.
I then have an onCreate function that will create a user in Firebase Authentication.
Would there be an issue with using the firestore docId as the created users uid?
Thanks
export const createUser = functions.region('europe-west2')
.firestore.document('users/{user}')
.onCreate(async (snap, context) =>
executeOnce(context.eventId, async (transaction: FirebaseFirestore.Transaction) => {
const data = snap.data();
await auth.createUser({
uid: snap.id,
email: data.email,
password: //,
displayName: `${data.firstName} ${data.lastName}`,
}).then(async function (newUser) {
return db.collection('users').doc(snap.id)
.update({
status: 'Active',
});
}).catch(function (error) {
return console.error(`Error creating user, ${error}`);
});
}));
I don't see why that would be an issue. Usually UID from Firebase Authentication is used as Firestore document ID. It's just an identifier so you can accurately point at the document containing current user's information. (<= just an example)
const uid = firebase.auth().currentUser.uid
const docRef = firebase.firestore().collection("users").doc(uid)
At the end, it's just a unique string. So as long as they both are same, you should be fine. Even if they are not, you could still query Firestore document using .where("userId", "==", uid).

Convert FirebaseUser to a customized User class in a Stream

I want to convert a FirebaseUser to a customized class by adding some extra fields in a Flutter project which is using Firebase as backend. Here is my code:
Stream<User> get user {
// return _auth.onAuthStateChanged.map(_fromFirebaseUser);
final theUser = _auth.onAuthStateChanged.map((firebaseUser) {
final result = Firestore.instance.collection("users")
.document(firebaseUser.uid).snapshots().map((snapshot) {
return User(
uid: user.uid,
name: snapshot.data['name'],
email: user.email,
age: snapshot.data['age'],
gender: snapshot.data['gender']
);
}
return result;
});
return theUser;
}
The basic idea is I will get the data from users collection and populate the User model. But I got the following error message:
The argument type 'Stream' can't be assigned to the parameter type 'Stream'.
Need your advice on how to return a Stream<User> instead of Stream<Stream<User>>. Thanks.
In this case, you're using 2 streams: _auth.onAuthStateChanged and Firestore.instance.collection("users").document(firebaseUser.uid).snapshots(). You need to either combine it, or simply asynchronously get the user document from the collection instead of listening for a stream:
Stream<User> get user {
return _auth.onAuthStateChanged.asyncMap((firebaseUser) async {
final snapshot = await Firestore.instance
.collection("users")
.document(firebaseUser.uid)
.get();
return User(
uid: firebaseUser.uid,
name: snapshot.data['name'],
email: firebaseUser.email,
age: snapshot.data['age'],
gender: snapshot.data['gender']
);
});
}
Note the using of asyncMap instead of map to allow asynchronous document fetch.

Reference.set failed: First argument contains undefined

I have created a firebase function that listen on onCreate event, however the DocumentSnapshot.data() is returning empty.
The function code is:
exports.createClientAccount = functions.firestore
.document('/userProfile/{userId}/clientList/{clientId}')
.onCreate(async (snap, context) => {
console.log('****snap.data(): ', snap.data()); //Showing Empty from the console.
return admin
.auth()
.createUser({
uid: context.params.clientId,
email: snap.data().email,
password: '123456789',
displayName: snap.data().fullName,
})
.then(userRecord => {
return admin
.database()
.ref(`/userProfile/${userRecord.uid}`)
.set({
fullName: userRecord.displayName, //ERROR here: Reference.set failed: First argument contains undefined
email: userRecord.email,
coachId: context.params.userId,
admin: false,
startingWeight: snap.data().startingWeight,
});
})
.catch(error => {
console.error('****Error creating new user',error);
});
});
The document IS created on the firebase database under
/userProfile/{userId}/clientList/{clientId}
clientId document created on the database
As per the documentation, onCreate listens when a new document is created and returns the snapshot of the data created through the DocumentSnapshot interface. However, I have checked from the console that snap.data() is empty. I don't understand why it is empty if the document is created successfully on the database.
image showing error returned by the functions when creating the userProfile
From the function code, return admin.auth.createUser({}) is creating the user as anonymous because snap.data().email is undefined, but it should create a non anonymous user.
First, please try to change document('/userProfile/{userId}/clientList/{clientId}') to document('userProfile/{userId}/clientList/{clientId}').
path should not start with /.
exports.createClientAccount = functions.firestore
.document('userProfile/{userId}/clientList/{clientId}')
At the end problem was that when I created the document with add({}) I was not including the fields in the instruction. This is the function that creates the client document and now the function gets triggered correctly.
async clientCreate(
fullName: string,
email: string,
startingWeight: number
): Promise<any> {
const newClientRef = await this.firestore
.collection(`userProfile/${this.userId}/clientList/`)
.add({
fullName,
email,
startingWeight: startingWeight * 1,
});
return newClientRef.update({
id: newClientRef.id
});
}
I was calling this.firestore.collection(...).add({}) with no fields, so when it happened, the cloud function got triggered and the DocumentSnapshot.data() was empty thus returning the Reference.set error. The cloud function createClientAccount is correct. Thanks.

Query firestore to get all tokens from userid's

In my cloud function i have an array that contains all userId's that need to get a cloud-message(notification)
const aNotify = [{id: 'id001', text: 'specialTextFor001'}, {id: 'id002', text: 'specialTextFor002'};
This is how the devices collection looks like. the Document ID is the token ID but to find them i need to query on the userId
Is it possible to do it through the DB like with a where clause or do I need to do this by getting all devices and in cloud method do a foreach... ?
In order to find a device document corresponding to a userId, you have to use a simple query like:
const db = admin.firestore();
db.collection('devices').where("userId", "==", element.id).get();
see the corresponding doc here.
Since you need to make a query for each element of the aNotify array, you need to use Promise.all(), since get() returns a Promise.
Something like the following will work. You have to adapt it in order to correctly return the promises in your Cloud Function (since you didn't share your Cloud Function code it is difficult to give more guidance on this point).
const db = admin.firestore();
var aNotify = [{ id: 'id001', text: 'specialTextFor001' }, { id: 'id002', text: 'specialTextFor002' }];
var promises = []
aNotify.forEach(function (element) {
promises.push(db.collection('devices').where("userId", "==", element.id).get());
});
return Promise.all(promises)
.then(results => {
results.forEach(querySnapshot => {
querySnapshot.forEach(function (doc) {
console.log(doc.id, " => ", doc.data());
//here, either send a notification for each user of populate an array, or....
//e.g. return admin.messaging().sendToDevice(doc.data().token, ....);
});
});
});
Note that the results array has exactly the same order than the promises array. So it is not complicated to get the text property of the corresponding object of the aNotify array when you send the notifications.

Firestore - How to get document id after adding a document to a collection

Is there a way to acquire the document id that was generated after adding a document to a collection?
If I add a document to a collection that represents a "post" in a social media app, I want to get that document id and use it as a field in another document in a different collection.
If I can't get the document Id that was generated after adding a document, should I just compute a random string and supply the id when creating the document instead? That way I can use that same string as the field in my other document?
Quick structure example:
POST (collection)
Document Id - randomly generated by firebase or by me
USER (collection)
Document Id - randomly generated by firebase
userPost: String (this will be the document id
in the post collection that I'm trying to get)
Yes it is possible. When you call the .add method on a collection, a DocumentReference object is returned. DocumentReference has the id field, so you can get the id after the document was created.
// Add a new document with a generated id.
db.collection("cities").add({
name: "Tokyo",
country: "Japan"
})
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
})
.catch(function(error) {
console.error("Error adding document: ", error);
});
This example is in JavaScript. Visit the documentation for other languages.
If using promises, I'd recommend using fat arrow function as it opens up the possibility for using this.foo even in the .then function
db.collection("cities").add({
name: "Tokyo",
country: "Japan"
})
.then(docRef => {
console.log("Document written with ID: ", docRef.id);
console.log("You can now also access this. as expected: ", this.foo)
})
.catch(error => console.error("Error adding document: ", error))
Using function(docRef) means you cannot access this.foo, and error will be thrown
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
console.log("You can now NOT access this. as expected: ", this.foo)
})
While fat arrow functions will allow you to access this.foo as expected
.then(docRef => {
console.log("Document written with ID: ", docRef.id);
console.log("You can now also access this. as expected: ", this.foo)
})
Edit/addition 2020:
A more popular way these days may be to use async/await instead. Notice that you have to add async in front of the function declaration:
async function addCity(newCity) {
const newCityAdded = await db.collection("cities").add(newCity)
console.log("the new city:", newCityAdded)
console.log("it's id:", newCityAdded.id)
}
And if you only want the id it can be grabbed using descructuring. Destructuring allows you to grab any key/value-pair in the response:
async function addCity(newCity) {
const { id } = await db.collection("cities").add(newCity)
console.log("the new city's id:", id)
}
It's also possible to use destructuring to grab the value and rename to whatever you want:
async function addCity(newCity) {
const { id: newCityId } = await db.collection("cities").add(newCity)
console.log("the new city's id:", newCityId)
}
If you want to use async/await instead of .then(), you can write it like this:
const post = async (doc) => {
const doc_ref = await db.collection(my_collection).add(doc)
return doc_ref.id
}
If you want to catch any errors in this function, include .catch():
const doc_ref = await db.collection(my_collection).add(doc).catch(err => { ... })
or you can have the calling function catch the error.
For Android, Java, you're suppose to get the Document ID before you set() or add() something to Firestore. Like so:
//Fields:
CollectionReference toolsCollectionRef = FirebaseFirestore.getInstance().collection(toolsCollection);
CustomPOJO_Model toolToPost;
//In Methods:
String newDocID= toolsCollectionRef.document().getId(); //Get Doc ID first.
toolToPost.setToolID(newDocID);
//Now use the doc ID:
toolsCollectionRef.document(newDocID).set(toolToPost.getConvertedTool_KeyValuePair ()).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
}
});
//Re-use same ID in another post:
usersCollectionRef.document(mAuth.getUid()).collection(usersToolsCollection).document(toolToPost.getToolID()).set(toolToPost.getConvertedTool_KeyValuePair());
using v9, you can also get the ID even before creating the document
Get a new docRef and read its random id
Use the id as you want
For example, insert the id in the document data
Then create the document
const usersRef = collection(db,'users') // collectionRef
const userRef = doc(usersRef) // docRef
const id = userRef.id // a docRef has an id property
const userData = {id, ...} // insert the id among the data
await setDoc(userRef, userData) // create the document
As others mentioned also, we can get the document reference once it added.
After we get the document reference on the behalf of id, we can update the same
Service.ts file
async funName(data: Data){
let docRef = this.firestore.collection('table-name').add(data);
console.log(docRef)
try {
const docAdded = await docRef;
console.log(docAdded.id);
this.firestore.doc('table-name/' + docAdded.id).update({ id: docAdded.id });
return docRef;
}
catch (err) {
return err;
}
}
component.ts file
async addData(){
try{
let res = await this.dataServ.funName(this.form.value);
this.snackbar.open('success', 'Success');
}catch(ex){
this.disabled = false;
this.snackbar.open('err', 'Error')
console.log(ex, 'exception');
}
}
for FB Firestore version 9 (JS/Web) use the following syntax:
import { addDoc, doc, Timestamp, updateDoc } from "firebase/firestore";
//add document to 'posts' collection with auto id
const newItem = await addDoc(collection(db, 'posts'), {
caption: post.value.caption || "No caption provided",
location: post.value.location || "No location provided",
imageUrl: imageUrl.value,
createdAt: Timestamp.now(),
});
//get new document id an update it to the file as id field.
const fileID = newItem.id
console.log('added file:', fileID);
const updateDocId = doc(db, "posts", fileID) ;
await updateDoc(updateDocId, {
id: fileID
})
I'm not sure why this one was voted out. This is what I needed I was looking for adding the doc().set() instead of doc().add().
I will be using uuid as the document to search for my users inside collection.
firebase.firestore().collection("cities").doc().set({ name: Tokyo,
country: Japan })
According to documentation in firebase v9 i think you'd want to use addDoc() method like so:
import { collection, addDoc } from "firebase/firestore";
// Add a new document with a generated id.
const docRef = await addDoc(collection(db, "cities"), {
name: "Tokyo",
country: "Japan"
});
console.log("Document written with ID: ", docRef.id);
There is also setDoc() method which allows you to set your own id for the document
import { doc, setDoc } from "firebase/firestore";
// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "YOUR_CUSTOM_ID"), {
name: "Los Angeles",
state: "CA",
country: "USA"
});
Here's what I do, as mentioned in your question. Not sure if it is best practice, but it works for easy access.
When creating the document in the first place
firebase.firestore().collection("cities").doc().set({ name: Tokyo,
country: Japan })
you can set the id of the doc and put that exact id in it as a property:
firebase.firestore().collection("cities").doc('id-generated-from-somewhere')
.set({ id: 'id-generated-from-somewhere', name: Tokyo, country: Japan })

Resources