How to add a document to a collection in cloud firestore - firebase

I have a collection called 'users'. I'm trying to add a user to the collection after Google authentication but I keep getting the following error:
FirebaseError: [code=invalid-argument]: Invalid document reference. Document references must have an even number of segments, but users has 1.
Here is the code
this.googlePlus.login({
'scopes': '',
'webClientId': environment.googleWebClientId,
'offline': true,
})
.then(user => {
// save user data on the native storage
const userRef: AngularFirestoreCollection<User> = this.afs.collection<User>(`users/`);
const data: User = {
email: user.email,
displayName: user.displayName,
uid: user.uid
};
userRef.set(data)
.then(() => {
this.router.navigate(['/home']);

Google+ is being discontinued so you should look at Firebase Authentication, or GCP's new Cloud Identity Platform.
In the case of Firebase Authentication, you must listen to the .onAuthStateChanged observer. Once it fires off your user object, you then take that and write a new user document to a users collection in Firestore. Best practise is to use the uid of the firebase.auth().currentUser.uid as the user document ID in your users collection.

Your userRef refers to a collection, and the type of object is called a CollectionReference. You're attempting to call set() on it with some object that should become a new document in that collection. But that's not the way it works. Instead, it looks like you want to call add() to add a new document with a new random ID.
If you somehow already know the ID of the new user document, you should build a DocumentReference with that id, then use set() on that DocumentReference to create the document.

Related

Firestore onAuthStateChanged and users in a subcollection

I'm building an app where users can authenticate, in Firestore I save extra data from that user (username, age).
Now in my app, users are coupled to events, I chose to have an events collection, which has a users subcollection.
I'm using the firebase onAuthStateChanged listener to see when my user has logged in. However the issue I'm not facing is, to get the firestore data for my user, I need to know which event this user belongs to, which is of course, data I do not have access to at the time the user signs in, for example:
const onAuthStateChangedPromise = new Promise((resolve, reject) => {
auth.onAuthStateChanged(async firebaseUser => {
if (firebaseUser !== null) {
const user = await getDoc(doc(db, 'events/${eventId}/users', id))
useAuth().user = user
return resolve(user)
}
return resolve(null)
}, err => {
reject(err)
})
})
In the example above, to get my user's data, I need to know the eventId, which I can not possible determine from the authenticated user.
I'm wondering how to achieve this?
I could save the eventId in localStorage as soon as the user registers, but that can cause issue's, since the complete app then relies on something being set on localStorage
The typical way to solve this would be to add the UID of the user in a field inside the events/${eventId}/users documents and then use a collection group query across all users collections. This will give you a list of all event/users docs for that user.
To find the event for such an event/user doc, you first take the DocumentReference for the DocumentSnapshot and then go up the parent chain twice to get to the parent event document.

How to automatically update same field in different collections in Firestore

I am using Cloud Firestore as my database and I have collections of users where are stored basic information about user such as id, name, last name, email, company id.
Also I have collection of companies and in each company I have collection of tasks.
In each task I have one user assigned from collections of users (user data is replicated, so I have same data for that user as in collection users)
The problem is when I update user (change name or email...) from collection users because data is replicated that data is not changed in collection of tasks for that specific user.
Is there any way that using firestore when user from collection users is updated to automatically update it in collection of tasks?
This is quite a standard case in NoSQL databases, where we often denormalize data and need to keep these data in sync.
Basically you have two possible main approaches:
#1 Update from the client
When you update the "user" document, update at the same time the other documents (i.e. "tasks") which contain the user's details. You should use a batched write to do so: A batch of writes completes atomically and can write to multiple documents.
Something along the following lines:
// Get a new write batch
var batch = db.batch();
var userRef = db.collection('users').doc('...');
batch.update(userRef, {name: '....', foo: '....'});
let userTaskRef = db.collection('companies').doc('...').collection('tasks').doc('taskId1');
batch.update(userTaskRef, {name: '....'});
userTaskRef = db.collection('companies').doc('...').collection('tasks').doc('taskId2');
batch.update(userTaskRef, {name: '....'});
// ...
// Commit the batch
batch.commit().then(function () {
// ...
});
Note that you need to know which are "the other ("tasks") documents which contain the user's details": you may need to do a query to get these documents (and their DocumentReferences).
#2 Update in the back-end via a Cloud Function
Write and deploy a Cloud Function that is triggered when any "user" document is updated and which takes the value of this "user" document and update the "tasks" documents which contain the user's details.
Like for the first approach, you also need, in this case, to know which are "the other ("tasks") documents which contain the user's details.
Following your comment ("Is there any option to reference to another table or put foreign key?") here is a Cloud Function that will update all the ("tasks") documents that have their DocumentReference contained in a dedicated Array field taskRefs in the "user" doc. The Array members are of data type Reference.
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const name = newValue.name;
const taskRefs = newValue.taskRefs;
const promises = taskRefs.map(ref => { ref.update({ name: name, foo: "bar" }) });
return Promise.all(promises);
});
You would most probably set the value of this taskRefs field in the "user" doc from your frontend. Something along the following lines with the JS SDK:
const db = firebase.firestore();
db.collection('users').doc('...').set({
field1: "foo",
field2: "bar",
taskRefs: [ // < = This is an Array of References
db.collection('tasks').doc('....'),
db.collection('tasks').doc('....')]
});

How to trigger onCreate() when a new field is created in an existing Firestore document?

I want to create a new node in firebase realtime database when a field is created in an existing Firestore document.
I have been trying this:
exports.addUserCredentials = functions.firestore
.document(`Users/{UserID}/{username}`)
.onCreate((snapshot, context) => {
const newUserData = snapshot.data()
const newUserUsername = newUserData
const newUserUidDoc = context.params.UserID
return admin.database().ref(`/userCredentials/${newUserUsername}`).set({"UID": newUserUidDoc})
})
I have searched around the web I saw the path must be directed towards a document only and not a collection. BUT username in the path is a FIELD in the document.
I am getting this error while deploying and I have seen all similar questions but those didn't perfectly answered mine:
! functions: failed to update function addUserCredentials
HTTP Error: 400, The request has errors
The reason is can't change my path to Users/{UserID} which will make my code run perfectly is the fields of document are not added at once.
Here is screenshot of my firestore structure:
The 4 fields of document are updated in 2 batches.
The EMAIL and timeCreated fields are added first and those create the document.
While on the other hand, phoneData and username are fields are CREATED [not updated] after 5 seconds of Email and timeCreated.
So if I use onCreate() on the path Users/{UserID}, it will return UNDEFINED to my realtime database as the username field is ABSENT at that instant.
Is there any way to apply onCreate() on a specific field of the document?
[I am doing this to create a separate node which contains username and UID, this is to check if an username exists when a new user is trying to sign up]
So if the node is created with value undefined it will be an issue.
It will be like this:
The EMAIL and timeCreated fields are added first and those create the document. While on the other hand, phoneData and username are fields are CREATED [not updated] after 5 seconds of Email and timeCreated.
No matter what fields are adding once you created a document,it will be considered as an update operation against that document.As you mentioned in the question,there will be no field with field name called username with a document while you creating document.So it is not possible to get the value of username while you creating the document.
According to your explanation the field username will be only available with the onUpdate trigger.
So the code should be something like below
exports.addUserCredentials = functions.firestore
.document(`Users/{UserID}`)
.onUpdate((snapshot, context) => {
const beforeData = snapshot.before.data()
const afterData = snapshot.after.data()
if(!beforeData.username && afterData.username){
return admin.database().ref(`/userCredentials/${newUserUsername}`).set({"UID": newUserUidDoc})
}
})

how to get data from cloud firestore where user.uid equal to document id in flutter?

I am Having this profile screen which shows users info.
after user authenticated I am storing data in cloud firestore with document id is equal to user-id.
Now, I want to retrieve data from cloud firestore with having current userId is equal to document id.
For now i have this :
class UserManagement {
getData() async{
String userId = (await FirebaseAuth.instance.currentUser()).uid;
print(userId);
return Firestore.instance.collection('users').document(userId);
}
but this is not working properlywhen i log out and after re-login with different user it showing me same data.
UserManagement().getData().then((results) {
setState(() {
userFlag = true;
users = results;
});
});
Now, how get other fields like name,email,course,phonenumber..etc
and all values all storing into user.right?
If the document id in your firestore is equal to the userid in the Firebase authentication console, then you need to retrieve the uid first and pass it as an argument to the method document():
getData() async{
String userId = (await FirebaseAuth.instance.currentUser()).uid;
return Firestore.instance.collection('users').document(userId);
}
Your query is fetching all of the documents in the "userData" collection, then picking out the first document from that entire set. This will be the same set of documents for all users that have read access to that collection. I don't see why you would expect a different result for different users. Perhaps you meant to access a single document for a user given their user ID, instead of all of the documents. If that's the case, you should request that document by its ID with Firestore.instance.collection('userData').document(uid)' whereuid` is the ID of the currently signed in user.
Also, your code is querying a collection called "userData", but your screenshot shows a collection called "users", so that is confusing.

How to create unique and safe UID on Admin- Custom Token- Firebase

I use custom auth function on my app, in order to login anonymous users (by saving a unique permanent custom token on their device).
The functions work fine both on app side and admin side but I need to provide a unique UID on the admin side- in order to save the user on Firebase.
Here's my code on Admin (index.js):
exports.createToken = functions.https.onCall((data, context) => {
const uid = "?????"; // how to create some unique uid here?
return admin.auth()
.createCustomToken(uid)
.then(customToken => {
console.log(`The customToken is: ${customToken}`);
return {status: 'success', customToken: customToken};
})
});
I need to create unique uid (that isn't on my Authentication users
table already).
It should be "safe" (that two users won't get it at the same time).

Resources