I'm trying to send Expo push notifications to multiple devices. I'm retrieving the Expo tokens from Firestore. When I enter the tokens manually, it works! It sends the notification to both devices I'm using, but when I retrieve the data from Firestore, it only sends the notification to one device.
async function sendPushNotification(readx) {
const message = {
to: readx,
sound: "default",
title: "Original Title",
body: "And here is the body!",
data: { someData: "goes here" },
};
const retrieveNetwork = async () => {
try {
//const querySnapshot = await getDocs(collection(db, "cities"));
const q = query(collection(db, "users"));
const querySnapshot = await getDocs(q);
setRead(querySnapshot.docs.map((doc) => doc.data().expoUser));
setReadx(JSON.stringify(read));
} catch (e) {
alert(e);
}
};
The retrieving of data from the firestore seems to be an issue , as your code is using the Snapshot for querying the data ,it should get the token id for both the devices in the loop and then return to the await sync to call the notification function.As per the Firebase documentation on reading multiple documents, you'll see that it uses the data() function on each DocumentSnapshot to get at the fields of that document.
So try to modify accordingly,like use doc.role and doc.token instead of doc.data().role and doc.data().token.
Check this example code below:
let tokenList = []; const userNotificationTokenDocs = await db.collection("userToken").doc(userId).get() .then(querySnapshot => { querySnapshot.forEach((doc) => { console.log(doc.data().Tokens); tokenList.push(doc.data().Tokens); }); return null; });
Also you may try adding the below to your code:
userToken.forEach((token) => { console.log(token); tokens.push(token); });
Checkout these following with similar implementation:
Push notification firestore
Triggering expo sdk to push notification to users
Notification to a collection of token
Array token sending notification
Just solved. Need to change
<Button
title="Press to Send Notification"
onPress={async () => {
await sendPushNotification(expoPushToken);
}}
/>
to
<Button
title="Press to Send Notification"
onPress={async () => {
await sendPushNotification(readx);
}}
/>
Related
I need to fetch the user token from the firestore in a cloud function.
the user token was stored as follows:
void saveToken(String token) async {
await FirebaseFirestore.instance
.collection("User tokens")
.doc(userId)
.set({'token': token});
}
here is the goal.
When a message is created on the collection 'chat messages',
grab the "Chat id" value and the user who sends the message "User id".
query the collection "chat" using the "Chat id" value,
grab the "Job users data" value (this is an array with two objects, each object contains the users involved in the chat (userName,userId) ).
from the "Job users data", I need to grab the userId of the member who should be receiving the message.
query "User tokens" collection to grab the "token" value.
use the "token" value, to send a notification to
here is my cloud function:
as you see, I have hardcoded the token to see if I could send that device a notification.... that works perfect. now I need to to make this dynamic...
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { database } = require("firebase-admin");
// eslint-disable-next-line max-len
const tokens = ["JNKDNASNDAUIU324234....."];
admin.initializeApp();
// exports.onCreate = functions.firestore
// .document("chat/{docId}")
// .onCreate((snapshot, context) => {
// console.log(snapshot.data());
// console.log("fake data");
// });
exports.onChatMessageCreate = functions.firestore
.document("chat messages/{docId}")
.onCreate( (snapshot, context) => {
console.log(snapshot.data());
// fetch user to send message to
// admin.database().ref("/")
const payload = {
// eslint-disable-next-line max-len
notification: {title: snapshot.data()["userName"], body: snapshot.data()["Chat message"], sound: "default"},
// eslint-disable-next-line max-len
data: {click_action: "FLUTTER_NOTIFICATION_CLICK", message: "Sample Push Message"},
};
try {
admin.messaging().sendToDevice(tokens, payload);
console.log("NOTIFICATION SEND SUCCESSFULLY");
} catch (e) {
console.log("ERROR SENDING NOTIFICATION");
console.log(e);
}
});
So all i need to know is how to query collections from a cloud function
There are 2 ways to query a collection in Node.js. either through then() or async/await.
to query using promise:
const collectionRef = admin.firestore().collection("yourCollection");
return collectionRef.get().then((collections) => {
//you can now use your collections here
});
using async/await:
const collectionRef = admin.firestore().collection("yourCollection");
const collections = await collectionRef.get();
In my app's main.dart, I ran the following code:
final fcmToken = await FirebaseMessaging.instance.getToken();
I took the token and used it in my cloud function:
exports.notifyUserAddedToGroup = functions.firestore
.document("groups/{groupDocID}/groupMembers/{groupMembersDocID}")
.onWrite((change, context) => {
const FCMToken = `loooooooooooooooooooooooooooooooong
fcmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
tokennnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn`;
const payload = {
token: FCMToken,
notification: {
title: "Title",
body: "Body",
},
data: {
body: "data body",
},
};
admin.messaging().send(payload)
.then((response) => {
console.info("##MyApp## function executed successfully");
return {msg: "##MyApp## function executed succesfully"};
})
.catch((error) => {
console.info("##MyApp## error in execution");
console.log(error);
return {msg: "##MyApp## error in execution"};
});
});
I then went to Firestore and added a document into the correct collection to trigger the cloud function. When I went to the google cloud console logs, I saw the following error:
The registration token is not a valid FCM registration token
Why is my token invalid if I just generated it a few minutes before triggering the cloud function?
The problem was the hard coded FCM token in the function. I put the token in Firestore instead and queried it to use it in the function and it worked.
In my Flutter app I use Firebase's phone number authentication as my main form of authentication. After authenticating, I create a user in my users collection with these details:
{
phoneNumber: FirebaseAuth.instance.currentUser().phoneNumber,
displayName: 'Comes from user textbox',
...
}
But say one day a user want's to change their phone number. How do I do this? Because I cannot simply change the user's phone number in the document, because the phone number needs to be authenticated. And after authentication, the user gets a new authUID. Which should then be a new user?
Could someone explain the logic behind a user that wants to keep their profile details but change their number.
In order to achieve this, you can use User.updatePhoneNumber. This allows you to update the phone number of a user.
You would use it in the same manner that you also authenticated with phone number in the first place (using signInWithCredential), i.e. you retrieve a credential using FirebaseAuth.verifyPhoneNumber and pass the credential that you get from either verificationCompleted or your user when they enter the SMS code they received. I will only sketch out what this would look like as I assume that you know how to perform this task:
FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(minutes: 2),
verificationCompleted: (credential) async {
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
// either this occurs or the user needs to manually enter the SMS code
},
verificationFailed: null,
codeSent: (verificationId, [forceResendingToken]) async {
String smsCode;
// get the SMS code from the user somehow (probably using a text field)
final AuthCredential credential =
PhoneAuthProvider.getCredential(verificationId: verificationId, smsCode: smsCode);
await (await FirebaseAuth.instance.currentUser()).updatePhoneNumber(credential);
},
codeAutoRetrievalTimeout: null);
When updatePhoneNumber is called, you probably also want to update your database document. Alternatively, you could listen to onAuthStateChanged and update your document this way.
async function save(phone: string, e) {
e.preventDefault();
const { currentUser:fuser } = firebase.auth();
if(fuser && fuser.phoneNumber !== phone) {
try {
const verifier = new firebase.auth.RecaptchaVerifier('recaptcha-container', {
callback: (response) => console.log('callback', response),
size: 'invisible',
});
const phoneProvider = new firebase.auth.PhoneAuthProvider();
const id = await phoneProvider.verifyPhoneNumber(phone, verifier);
const code = window.prompt('Bitte zugeschickten Code eingeben');
const cred = firebase.auth.PhoneAuthProvider.credential(id, code);
await fuser.updatePhoneNumber(cred);
console.log('phone number changed', id, cred, fuser);
setSuccess(true);
} catch(e) {
console.error(e);
}
}
}
I'm using Firebase functions for creating seller account but I don't know how to create seller account and what to put in the redirect_url
I followed some tutorials and wrote the below code
Let me know what changes should I do to open seller account registration with url_launcher
Thanks
const stripeAccount = functions.https.onRequest(async (req, res) => {
const { method } = req
if (method === "GET") {
// CREATE CONNECTED ACCOUNT
const { mobile } = req.query
const account = await stripe.accounts.create({
type: "express",
})
const accountLinks = await stripe.accountLinks.create({
account: account.id,
refresh_url:, <-- What to put here
return_url:, <-- What to put here
type: "account_onboarding",
})
if (mobile) {
// In case of request generated from the flutter app, return a json response
res.status(200).json({ success: true, url: accountLinks.url })
} else {
// In case of request generated from the web app, redirect
res.redirect(accountLinks.url)
}
} else if (method === "DELETE") {
// Delete the Connected Account having provided ID
const {
query: { id },
} = req
console.log(id)
const deleted = await stripe.accounts.del(id)
res.status(200).json({ message: "account deleted successfully", deleted })
} else if (method === "POST") {
// Retrieve the Connected Account for the provided ID
// I know it shouldn't be a POST call. Don't judge :D I had a lot on my plate
const account = await stripe.accounts.retrieve(req.query.id)
res.status(200).json({ account })
}
const stripeReAuth = async (req, res) => {
const { account_id: accountId } = req.query
const accountLinks = await stripe.accountLinks.create({
account: accountId,
refresh_url: <-- Here
return_url: , <-- Here
type: "account_onboarding",
})
res.redirect(accountLinks.url)
}
})
This is my flutter code, I'm retrieving the return_url and launching it with url_launcher
class StripeBackendService {
static String apiBase = '{function address}/stripeAccount';
static String createAccountUrl =
'$apiBase/account?mobile=true';
static String checkoutSessionUrl =
'${StripeBackendService.apiBase}/checkout-session?mobile=true';
static Map<String, String> headers = {'Content-Type': 'application/json'};
void createSellerAccount() async {
var url = Uri.parse(StripeBackendService.createAccountUrl);
var response = await http.get(url, headers: StripeBackendService.headers);
Map<String, dynamic> body = jsonDecode(response.body.toString());
await canLaunch(body['url']) ? await launch(body['url']) : throw 'Error'
}
}
The refresh url should point to an address that retries the creation of the stripe connect account, in case your current http function returns an expired link. The return url is the address that the potential stripe connect user gets sent to after the stripe onboarding is complete. In knowing that address you can use the webview controller to jump back to the app when reaching that return-url endpoint.
I want to store the onesignal User Id in the database when the user install my app first time. I wrote the following code which is not allowing me to do so.
However when the user Logged out from the app and re-login then I am able to retrieve the onesignal user ID and able to save in the Database.
Future<void> initPlatformState(username) async {
if (!mounted) return;
OneSignal.shared.setLogLevel(OSLogLevel.verbose, OSLogLevel.none);
OneSignal.shared.setRequiresUserPrivacyConsent(_requireConsent);
var settings = {
OSiOSSettings.autoPrompt: false,
OSiOSSettings.promptBeforeOpeningPushUrl: true
};
OneSignal.shared
.setSubscriptionObserver((OSSubscriptionStateChanges changes) {
print("SUBSCRIPTION STATE CHANGED: ${changes.jsonRepresentation()}");
});
OneSignal.shared.setPermissionObserver((OSPermissionStateChanges changes) {
print("PERMISSION STATE CHANGED: ${changes.jsonRepresentation()}");
});
OneSignal.shared.setEmailSubscriptionObserver(
(OSEmailSubscriptionStateChanges changes) {
print("EMAIL SUBSCRIPTION STATE CHANGED ${changes.jsonRepresentation()}");
});
await OneSignal.shared
.init("MY Onesignal APP ID", iOSSettings: settings);
OneSignal.shared
.setInFocusDisplayType(OSNotificationDisplayType.notification);
var status = await OneSignal.shared.getPermissionSubscriptionState();
onesignalUserId = status.subscriptionStatus.userId;
print("player ID: "+ onesignalUserId.toString()); // printing only in re-login
_saveOneSignalId(onesignalUserId,username); // my save funtion into the DB
}
You can call the below function inside your main.dart file when initialising the One Signal to get playerId/userId. The below function is called when user opend the app for first time or when the userId (playerId) changes.
OneSignal.shared.setSubscriptionObserver((OSSubscriptionStateChanges changes) async {
String onesignalUserId = changes.to.userId;
print('Player ID: ' + onesignalUserId);
}
The playerId/userId can be used to send test notification or notifications to particular user.
Please try this.
OneSignal.shared.setSubscriptionObserver((OSSubscriptionStateChanges changes)
async{
var status = await OneSignal.shared.getPermissionSubscriptionState();
if (status.subscriptionStatus.subscribed){
String onesignalUserId = status.subscriptionStatus.userId;
print('Player ID: ' + onesignalUserId);
}