I'm new to firebase and there is something I can't do. I want to send a notification to the phone with firebase functions. I want to receive notifications on the phone when someone follows me. My Firebase collection is as in the photo. I want to access the Followers array and send its information with notification. The codes I could write are as follows. What do I need to add?
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.sendPushNotification = functions.firestore.document('/users/{uid}').onCreate((snap, context) => {
var values = snap.data();
var token = values.fcmTokens;
var payload = {
notification: {
title: values.title,
body: values.message
}
}
return admin.messaging().sendToDevice(token, payload);
});
First, onCreate() function is triggered when a document is created. I assume followers array will be updated everytime someone follows a user? In that case you should be using onUpdate() that'll trigger the function when the document is updated. You can just check if length of followers array has changed in the update, if yes then send the notification as shown below:
exports.sendPushNotification = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
if (newValue.followers.length > previousValue.followers.length) {
// followers count increased, send notification
const token = newValue.fcmTokens;
const payload = {
notification: {
title: "New Follower",
body: "Someone followed you"
}
}
await admin.messaging().sendToDevice(token, payload);
}
return null;
});
Here, we send notification only if the followers field has changed since this function will trigger whenever any field in this user document is updated.
If you want to specify who followed the user, then you'll have to find the new UID added in followers array and query that user's data.
Firestore documents have a max size limit of 1 MB so if a user can have many followers then I'll recommend creating a followers sub-collection. Then you'll be able to use onCreate() on the sub-document path /users/{userId}/followers/{followerId}
Related
So far I have written the following code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.addAccount = functions.auth.user().onCreate((event) => {
const user = event.data; // The firebase user
const id = user.uid;
return admin
.database()
.ref("/users/" + id)
.set("ok");
});
The idea here is to execute a cloud function once a new user gets created. As this happens, I want to create a new node in the Firestore database. Under the 'users' collection I want to add a document with the uid, which contains a bit of information, in this case a String that says 'ok'.
When deploying this function it produces an error. How exactly should I go about creating it?
The admin.database() returns the Realtime Database service. To use Firestore, use firebase.firestore(). Try refactoring the code as shown below:
exports.addAccount = functions.auth.user().onCreate((event) => {
const user = event.data; // The firebase user
const id = user.uid;
return admin
.firestore()
.collection("users")
.doc(id)
.set({ uid: id, ...user });
});
I am building a user auth system with Nextjs
I am trying to create a document within firestore for each user in my firebase authentication system. I was easily able to do this in previous projects when creating an account with email and password but with the 'sign in with google' feature I can't seem to figure out how.
I don't want to create a new document every time the user logs in..
My only idea is this:
When user signs in, loop through all firestore documents and see if the users e-mail matches any firestore doc email. If not, create document, else return.
I feel like there is another way though..
Simplest way would be to make a custom hook that can be used anywhere across the application.
First in the _app file inside useeffect hook simply try to get the data from doc if data exist well it means user document is already there and if data does not exists, we need to create a document for that, quite simple. Let's see the code now
Make sure you read comments written inside the code to better understand
In _app.js,
useEffect(async () => {
// now this checks if user is logged in or not
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
// if logged in it simply passes the userAuth object to handle user profile
// which is a custom hook to check if document for this user pre-exist or not!
// if there wont be any document it will go and create a document and return
// that document.
// If already there is a document created it will simply return that.
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot((snapshot) => {
// later you can save currentUsr value in any of the state to use it later
const currentUsr = {
id: snapshot.id,
...snapshot.data(),
};
}
});
}
}
});
return () => unsubscribe();
}, []);
Now the custom hook to check if document is already there or not, here comes the tricky part.
export const handleUserProfile = async (userAuth) => {
// as a second check it check if falsy values are returned
if (!userAuth) return;
const { uid } = userAuth;
// first it tries to get data from that uid
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
// checks if snapshot exist
if (!snapshot.exists) {
// if snapshot does not exist, it will simply create a document with document
// name as this 'uid'
const { displayName, email } = userAuth;
const timeStamp = new Date();
try {
// making use of same userRef that we created above to create
await userRef.set({
displayName,
email,
createdAt: timeStamp,
});
} catch (error) {}
}
// if snapshot exist it will simply return the userRef which contains the
// document only.
return userRef;
};
Voila! :)
There is no reason why you should not use the onAuthStateChanged event on auth. A write would cost you the same as a read to check if the data is already there. But with a read you would sometimes need also a write. In total only writes every time come less expensive in read/write actions.
Just listen to auth state changes and update your firestore data each time it changes:
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
await firebase.firestore()
.collection("users")
.doc(user.uid)
.set(data, {merge:true});
// User is signed in.
}
});
Make sure to use set with merge turned on. That will ensure that the data will be created if it doens't exist and update only the field you want to update.
Also make sure to store the data under the user uid. With that you ensure that each user has an unique idenfier. It is a bad practice to store users under the email. One of the reasons for that is that emails could have chars that are not supported as keys so would need to remove those when saving and add them again when reading the keys.
Firestore won't create duplicate docs if created when signing in with Google.. so this works:
const signInWithGoogle = () => {
fire
.auth()
.signInWithPopup(google_provider)
.then((result) => {
/** #type {firebase.auth.OAuthCredential} */
var credential = result.credential;
// This gives you a Google Access Token. You can use it to access the Google API.
var token = credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
})
.catch((error) => {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
})
// CREATE USER DATA IN FIRESTORE
.then(async () => {
const data = {
//ADD DATA HERE
};
await fire
.firestore()
.collection("users")
.doc(fire.auth().currentUser.email)
.set(data);
});
};
Just starting to use Firebase functions and have the sample working, but confused because the update event doesn't occur if I change the 'messages' collection to a different name, eg 'listings'. I change the word 'messages' in two places, on the 'add' and the 'makeUppercase' line. I get the response OK, it writes the data to the collection, but doesn't fire the event. Must be simple, but can't google it.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the location parameter.
const inputcode = req.query.code || 'blank';
// Push the new message into Cloud Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('messages').add({inputcode: inputcode});
// Send back a message that we've succesfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Cloud Firestore.
const inputcode = snap.data().inputcode;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, inputcode);
const areacode = inputcode.toUpperCase();
const written = new Date();
return snap.ref.set({written, areacode}, {merge: true});
});
I'm using the local firebase emulator to do this test, by the way.
This is the new version, ony changing 'messages' to 'vvvv' in two places.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onRequest(async (req, res) => {
// Grab the location parameter.
const inputcode = req.query.code || 'blank';
// Push the new message into Cloud Firestore using the Firebase Admin SDK.
const writeResult = await admin.firestore().collection('vvvvv').add({inputcode: inputcode});
// Send back a message that we've succesfully written the message
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
exports.makeUppercase = functions.firestore.document('/vvvvv/{documentId}')
.onCreate((snap, context) => {
// Grab the current value of what was written to Cloud Firestore.
const inputcode = snap.data().inputcode;
// Access the parameter `{documentId}` with `context.params`
functions.logger.log('Uppercasing', context.params.documentId, inputcode);
const areacode = inputcode.toUpperCase();
const written = new Date();
return snap.ref.set({written, areacode}, {merge: true});
});
OK. Doug, your suggestion sank in after an hour or so! I've restarted everything and think that I understand. If I change the name in those two places, without restarting, the collection.add function takes place and I can see the record in the new collection, but the onCreate event didn't fire. I had to restart the whole service to restart buth parts. I was getting confused because one part was working and not the other. Thanks for your patience.
I am pretty new to DialogFlow. I am wondering how can I retrieve data from Firebase through the Inline Editor of DialogFlow. Hope you can help me!
Thats how you can communicate with firebase from dialogflow
const functions = require('firebase-functions');
const firebaseAdmin = require('firebase-admin');
const DialogflowApp = require('actions-on-google').DialogflowApp;
Initialize Firebase Admin SDK.
firebaseAdmin.initializeApp(functions.config().firebase);
Interaction with firebase Collection users in fulfillment function
let userId = app.getUser().userId;
admin.firestore().collection('users').where('userId', '==', userId).limit(1).get()
.then(snapshot => {
let user = snapshot.docs[0]
if (!user) {
// If user is not in DB, its their first time, Welcome them!
app.ask('Welcome to my app for the first time!');
// Add the user to DB
firebaseAdmin.firestore().collection('users').add({
userId: userId
}).then(ref => {
console.log('Added document with ID: ', ref.id);
});
} else {
// User in DB
app.ask('Welcome back!')
}
});
}
// Map function hanlder to Dialogflow's welcome intent action 'input.welcome'
const actionMap = new Map('input.welcome', start)
app.handleRequest(actionMap);
In my app, I am sending push notifications to the followers of a user when that user creates a new post. As you can see in the code below, I have some additional settings that I need to query from each follower's profile to get their push token and check for some additional notification settings. I am afraid that this query of each user's profile might become a bottleneck if a user has a large number of followers i.e. 1000.
What is the best way to approach this?
// The cloud function to trigger when a post is created
exports.newPost = functions.database.ref('/posts/{postId}').onCreate(event => {
const postId = event.params.postId;
const post = event.data.val();
const userId = post.author;
let tokens = [];
let promises = [];
return admin.database().ref(`/followers/${userId}`).once('value', (followers) => {
followers.forEach((f) => {
let follower = f.key;
promises.push(
admin.database().ref(`users/${follower}`).once('value')
);
});
})
.then(() => {
return Promise.all(promises).then((users) => {
users.forEach((user) => {
const userDetails = user.val();
if (userDetails.post_notifications) {
if(userDetails.push_id != null) {
tokens.push(userDetails.push_id);
}
}
})
})
})
.then(() => {
if (tokens.length > 0) {
const payload = {
notification: {
title: 'New Post!',
body: 'A new post has been created'
}
};
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload);
}
});
})
EDIT:
We have thought about using topics. But we are not sure how we can still have our customized notifications settings working with topics. Here's our dilemma.
We have multiple actions that can create notifications and we provide individual switches for each type of notification in the app so a user can select which type of notifications they want to switch off.
Let's say when User A follows User B. We can subscribe User A to "User B's topic" so whenever User B performs an action which sends out notifications to his/her followers, I can send send out notifications to users subscribed to "User B topic".
Because we have multiple notification switches in the app and when user A changes his/her settings that they do not want notifications for new posts but still want other types of notifications from the users he/she follows, we have not been able to figure out how we can use topics in this case.
Instead of using tokens, you can use topics for this. So lets say a user started following someone, then he will register to that topic.
Lets say he followed someone called "Peter", then you can execute this:
FirebaseMessaging.getInstance().subscribeToTopic("Peter");
Now if you have this database:
posts
postid
postdetails: detailshere
author: Peter
then using onCreate():
exports.newPost = functions.database.ref('/posts/{postId}').onCreate(event => {
const postId = event.params.postId;
const post = event.data.val();
const authorname = post.author;
const details=post.postdetails;
const payload = {
data: {
title:userId,
body: details,
sound: "default"
},
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24
};
return admin.messaging().sendToTopic(authorname, payload, options);
});
You can use this, everytime the author creates a new post, onCreate() is triggered then you can add the details of the post and the author name in the notification (if you want) and sendToTopic() will send it to all users subscribed to the topic that is the authorname (ex: Peter)
After your edit, I think you want the user to be unsubscribed from a topic, but stay subscribed to other topics, then you have to use the admin sdk for this:
https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions
Using the admin sdk you can unsubscribe the user also from a topic, a simple example:
// These registration tokens come from the client FCM SDKs.
var registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// ...
'YOUR_REGISTRATION_TOKEN_n'
];
// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
admin.messaging().unsubscribeFromTopic(registrationTokens, topic)
.then(function(response) {
// See the MessagingTopicManagementResponse reference documentation
// for the contents of response.
console.log('Successfully unsubscribed from topic:', response);
})
.catch(function(error) {
console.log('Error unsubscribing from topic:', error);
});