I am trying to implement Firebase cloud messaging in my Android app through a Node.js server and I have got stuck at a usecase.
I saw the Firebase tutorial of creating a device group using registration tokens to send messages/notifications to all devices with the same user logged in, what I don't understand is what happens when one of the registration tokens get refreshed by onTokenRefresh() method.
How will I distinguish which token to change as all will be belonging to the same user?
Update:
Ok, so now I have got stucked on another blocking use case. I am creating a user group identified by the user id from my server. If user uninstalls and reinstalls the app immediately and another user logs in on the device, if I call a gcm message on the previous user group this device still receives it.
Is there any way for the gcm to identify is the device it is sending the notification to is logged in or not and if it is, is it logged in with the same user as for the group?
There is another way to solve this problem using Cloud Firebase Functions.
How will I distinguish which token to change as all will be belonging
to the same user?
Using Firebase Functions, you don't have to. Within onTokenRefresh(), you send the new token to the server.
For Example:
The user has 3 devices, each of which have a token that has been sent to server.
*** deviceTokenA/B/C represent UIDs of the token ... we do not know what they are, or which device they belong to.
UserId:
Device Tokens:
deviceTokenA: true,
deviceTokenB: true,
deviceTokenC: true,
Now, the User is on the device that triggered deviceTokenA. The token is refreshed, and onTokenRefresh() is called, sending the token to that collection.
onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
sendTokenToServer(refreshedToken);
}
sendTokenToServer(String refreshedToken) {
// send to Firebase or Firestore Database, put in the Device_Tokens collection. }
Now, you will have 4 tokens in the collection.
UserId:
Device Tokens:
deviceTokenA: true, // this one has been "unregistered"
deviceTokenB: true,
deviceTokenC: true,
deviceTokenD: true, // this one has been registered.
The deviceTokenA no longer applies, because it was refreshed, and is not attached to an actual device anymore.
When looking at the device Tokens, we still don't know which ones are good, which are bad, and which tokens belong to which device. That's ok!
So, then create a forEach loop, getting each Token, and then send an FCM to each of these Tokens, FCM can let us know which tokens were sent successfully. One of them will return an error. If it returns an error saying the token was bad, we can then catch the error and delete that token, so it will not be called again.
// outside for Each loop
var promiseHolder = [];
// create a forEach loop, iterating through the collection of deviceTokens
// within that loop, put:
let innerPromise = admin.messaging().send(message)
.then(response => {
console.log('notification sent success: ' + response);
})
.catch((error) => {
console.log('Error sending notification: ' + error);
// if the error == bad token message, then Delete the Token.
if (error == 'Error: Requested entity was not found.') {
console.log('you matched the error, token doesn't work, handle here.');
//delete the old token
return admin.firestore()doc(`users/${userID}/device_tokens/${token_id}`).delete();
}
}
// still within forEach loop
promiseHolder.push(innerPromise);
// end the forEach Loop, and outside forEachLoop put:
return Promise.all(promiseHolder);
So I've been thinking about how to go with this scenario. First off, let's put in the instances when onRefreshToken() is called:
This will not be called very frequently, it is needed for key rotation and to handle Instance ID changes due to:
App deletes Instance ID
App is restored on a new device
User uninstalls/reinstall the app
User clears app data
Guess with that, you can say that 'onTokenRefresh()` will be called after one the above happens and if the device is online (of course it has to be online on order to get a new token). So I guess here's how I'd go on the scenario:
First off, upon registration, I would save the registration token and pair it along another identifier, let's say a deviceId (since we're in a scenario for a user with multiple devices) in my App Server.
So assume I add in 3 registration tokens, those are also paired with their deviceIds. I add them all to a device group.
Now say one of the devices triggers the onTokenRefresh(), I would immediately send a delete request to my App Server for the registration token that is currently paired to that deviceId (you should also delete it in any device group(s) it's connected to), replacing it with the new one, then re-add it to the corresponding device group(s).
That's the simplest way I can think of. The key here is for you to pair the registration tokens with another identifier and use it to find which registration token you need to replace.
at moment i use this method. in my database i create a node with devices id
deviceId: {
uid1: deviceId,
uid2: deviceId,
uid3: deviceId
}
another node with the users that are subscribed to receive a notifications
newsSubscriber: {
uid1: [array of subscribers],
uid2: [array of subscribers],
uid3: [array of subscribers]
}
when i can send a notification by my nodejs script i get all users that are saved in the newsSubscriber node, and for any user i get his deviceId and i put it into an array of devices to send a notification.
There are only one problem, i read now that in this mode there are a limit of only 20 devices!.
but i think that this is a good easy method to have a corresponding deviceId for any user because when i use the logout or the login in my app i can change the correspondent deviceId for any user, so as to have consistency between user and device.
what happen if to bypass the limit of 20 devices i send the same notification to different groups of 20 devices?
Related
I have an app where a user can register/login via Firebase. A user can have multiple devices among which all his data is being shared (of course he must be logged in). I keep track of all devices by their firebase device token and send appropriate update notifications when the user updates something on a particular device
Now I know that the firebase token is being refreshed, but how do I know that a token is invalid? Lets say a user has 4 devices where he is logged with one account. Now he delete the app on one of them, and installs it again, so he gets a new token. This means that now I have 5 device tokens on my server but still just 4 devices. The best approach would be to tie a token to some non-changeable device id like MAC oder IMEI but because of privacy policies that is not possible.
Is there some other way to fish out the tokens that have been revoked/invalidated?
The common way to detect expired/revoked FCM tokens is during sending of messages. FCM will at that point tell you exactly which tokens have expired, and you can then remove them from your database.
For an example of this, see this Node.js code from the functions-samples repo:
tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
const response = await admin.messaging().sendToDevice(tokens, payload);
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
I am using Cloud Firestore to keep app tokens to send push notifications. However, when the user uninstalls and reinstalls the app, Firestore receives a different token for the same user. How can I delete the relevant row of the previous token when the user uninstalls the app?
Thanks in advance.
Usually you'll want to detect when a token becomes invalid, and remove it at that time. E.g. when a token gets cycled (which happens every few weeks while the user has the app installed), you'll want to use that moment to remove the old token from your database and add the new one. Doing so minimizes the number of outdated tokens you have in your database.
So in steps that means in onTokenRefresh():
Check if there is a token in local storage (e.g. shared preferences). If so, remove that token from both the database and local storage.
Store the new token in both the database and local storage.
But in your case that is not possible, since onTokenRefresh won't be called when the app is uninstalled, and you won't have knowledge of the previous token when it gets reinstalled.
The easiest way to deal with outdated tokens left behind in this and other ways is to delete them when sending to that token fails. The sample of sending FCM notifications using Cloud Functions has a good example of that:
admin
.messaging()
.sendToDevice(tokens, payload)
.then((response) => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensSnapshot.ref.child(tokens[index]).remove();
}
}
});
});
It's quite simple, when the user reinstalls the app and logs in again, just override the old token with the new one. If you have also other stuff that needs to be deleted once the new token is generated, just check the existing token with the new one. If the tokens are different, you may delete all the unnecessary stuff.
I am new to firebase I am learning it like a toddler learning to walk. So far I have managed to send a message to my own phone using a token generated for my phone by firebase framework. Now here's where it gets tricky (in my opinion). There is a method called onTokenRefresh() in the FirebaseInstanceIdService extended service. Since it is called refresh, Then I am assuming that it will change. I want to know when this token is created and when will it be changed?
And if it changes, suppose I send a message to a device with token 'A' which is offline for now, so it will be queued. Now when the device gets online, it will "refresh" the token to 'B'. Now as the message was supposed to be delivered to token 'A', the message will never be delivered. How can I manage this situation?
The token is generated, after the app is first launched, as soon as the phone can connect to the Google servers. Due to the required connectivity this might not happen immediately, but in most of the cases it will happen in few seconds after the user open the app.
As soon as the token is generated the method onTokenRefresh() is called.
As you pointed out the token can change, in which case the onTokenRefresh() method will be called again.
The refresh event is somehow rare, don't expect to see it often at all.
When the refresh token happens, all the messages that have been "successfully" sent (the API returned you a message-id) to the old token will be delivered.
Finally, even after the refresh happened the old token will still be working for a short period, to allow the app to communicate the new token to its back-end.
On initial startup of your app, the sdk of FCM generates the registration token for the client app instance. As above said, It is a rare event. To be specific,The registration token may change when:
The app deletes Instance ID.
The app is restored on a new device
The user uninstall/reinstall the app
The user clears app data.
Instance ID provides a unique ID per instance of your apps.Instance ID provides a simple API to generate security tokens that authorize third parties to access your app's server side managed resources.The Instance ID server can even tell you when the device on which your app is installed was last used.We can use this to decide whether to keep data from the app or send a push message to re-engage with the users.
Every time the device token is changed, It is reflected in onTokenRefresh() method.For getting the device token when it is changed, we can call this method to get the refreshed token.
and to get the device token at any time we can use FirebaseInstanceId.getInstance().getToken() method to get the current device token.It takes a bit of time to get the device token.
Click here to read more about accessing device registration token.
onTokenRefresh() and FirebaseInstanceIdService are deprecated.
This call is also deprecated FirebaseInstanceId.getInstance().getToken()
Instead, You should override onNewToken(String token) in FirebaseMessagingService. This method triggered when the token is changed. Once you override this method, you can safely remove FirebaseInstanceIdService whcih contains onTokenRefresh().
When token can change?
App deletes Instance ID
App is restored on a new device
User uninstalls/reinstall the app
User clears app data
How to retrieve the current token:
by calling FirebaseInstanceId.getInstance().getInstanceId():
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
#Override
public void onComplete(#NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}
// Get new Instance ID token
String token = task.getResult().getToken();
// Log and toast
String msg = getString(R.string.msg_token_fmt, token);
Log.d(TAG, msg);
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});
For more info:
https://firebase.google.com/docs/cloud-messaging/android/client
For Managing tokens for specific sender id (other than the default sender id),
check here
I have app that allows user to login with credentials issued by the server, lets say simple login with password and usename.
So user starts the app, enter username and password, server authentificates the user, save this information. (details are really don't play any role in this case).
What I need to implement ?
There some events when my server need to notify user. Lets consider order status changed. If so I need to notify user about this, with notification in the status bar.
I have decided to use GCM for this task.
I read about this, and got some questions.
When user gets registration ID from GCM service in android, is it constant or it can be changed. As I understand there is no guarantee that it be always the same. So how can I handle refreshes of this ID.
Or if I will not call GoogleCloudMessaging.getInstance(applicationContext).registerit will stay the same until I register new or even if I call register method it can return the same ID. May I save this ID in SharedPreferences for example to use it for a long time ? Or it can be become invalid after some period of time ?
The main problem is that I need to handle multiple users on the same device. As far as I know, registration id issued by gcm service it tied to the device and app. So if new user logged out, and logged in with new credentials (new user account) how can I handle this ? I can for example store this ID in my database on the server and use it until user logout from the account inside my app, and after that just remove it from the database, and if user on this device will login with another account registration ID can be used to notify another user ? Will it work ? Or I have missed something.
How can I handle user multiple device. My group notifaction key will be some unique data from user account (email,username..). If I understand correctly, I need to get registration ID on all devices where user logins with its my server account and map this to one notification key. Should I handle notification dismiss by myself and than send upstream message to the GCM server in order to notify other user devices that notification has been dismissed ?
Please help with understanding of this service.
Thanks in advance.
I have implemented this in one of our app. In this we send the notification to users whenever new listing get added.
User can login from multiple devices same time. Device may be android or iOS anything.
Also multiple users can login from same device (After logging out previous users).
Whenever user log in from Android device will get the registration ID from GCM. This GCM id is not constant for one device, it gets changed whenever you again request it from GCM server.
We send the notifications to multiple devices using notification key, it contains GCM register ids of all devices. If you lose a notification key, you can’t get it back. To make it worse, You can’t regenerate a new notification key with a previously-used notification key name.
Never use unique data from user account (email, username..) as notification key name. Always generate some random string as notification key name.
To overcome this follow following steps:
Create one table to store the following details
Columns :
id
userId
deviceGCMId
deviceId
notificationKey
notificationKeyName
whenever user logs in sent the following parameters to the server.
GCMId, deviceId, userId
First check that userId and deviceId exists or not.
If not exists then go to step 5 else go to step 6
From given userId get the notificationKey and notificationKeyName from table. If not found then generate new notificationKeyName and from that key generate new notificationKey. Use current GCMRegId to generate the key. Store the keys in variables.
Add new entry in table with notificationKey and keyname generated in step 4. Also add the GCM id under notification key.
Check the deviceId is same and GCM id is different then add update the GCM id in table and add that GCM id under notification key. If device id and GCM id same then no need to do anything
Sometimes notification key get discarded from GCM server and it shows the key not found error. In this case create the new notificationKey from new key name and add GCM ids against that key.
For more info go through following useful links
https://medium.com/appunite-edu-collection/notifications-d7df385b0ff4
https://medium.com/#Bicx/adventures-in-android-user-notifications-e6568871d9be
Hope this will help
this is a common situation that lot's of apps facing, but I'm straggling to understand how to implement:
let's say that my application is a social network with a current logged in user.
I'd like to send GCM messages to that user, for all of his devices that currently logged in with my app.
that's mean that my server holds for each user a list of all of his registration ID's - one for each of his devices.
the problem: how can I track uniquely each one of his devices? seems there is no reliable way to get specific device identifier
without storing registration id for each unique device - I don't see how can I manage it.
things get messy when user will uninstall/logout and acquire new registration id afterwards, that suppose to replace one of the existing known id's, but which one of them??
things will get even more problematic if Id like to send message only to a specific device, and not all of them...
please help me understand what I missing, and what is the right way handling multiple devices registration id's for same user.
Google handles pretty much all the hard work for you when working with GCM. And they provided a simple method to always keep the registration id up to date. There is an extra field on each message sent called canonicalRegistrationId. If there is an id in that field, than the registration id has changed and needs to be updated. This field exists on every message and every time you send one, you have to check that field. If there is a new canonicalRegistrationId then you should update the registrationId as soon as possible. The old one may continue to work for some time, but there is no telling when it becomes invalid.
For example in a Google App Engine backend the code which handles the changing registration ids would look something like this:
// We want to send a message to some devices
// Get registered devices and then loop through them
List<RegistrationRecord> records = ofy().load().type(RegistrationRecord.class).limit(10).list();
for(RegistrationRecord record : records) {
// Send the message to one device
Result result = sender.send(msg, record.getRegId(), 5);
// If the messageId is not null, then the message has been sent successfully
if (result.getMessageId() != null) {
log.info("Message sent to " + record.getRegId());
// Check for canonical message id
// If it is not null, then the registrationId has changed
String canonicalRegId = result.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// The registrationId has changed! We need to update it in the database
log.info("Registration Id changed for " + record.getRegId() + " updating to " + canonicalRegId);
record.setRegId(canonicalRegId);
ofy().save().entity(record).now();
}
} else {
... // Irrelevant error handling
}
}
So what you have to do is pretty simple: Every time you send a message, check if there is a canonicalRegistrationId, if yes then update the old registrationId with the canonical one.
Google supports device group messaging. Refer to this link: https://developers.google.com/cloud-messaging/notifications#managing_device_groups
To handle multiple users you can create a group for that user and add the registration id to that group. To create a group send a request like the following to https://android.googleapis.com/gcm/notification:
Content-Type:application/json Authorization:key=API_KEY project_id:SENDER_ID
{ "operation": "create", "notification_key_name": "appUser-Chris", "registration_ids": ["4", "8", "15", "16", "23", "42"] }
This API returns a notification key in response which you can store in your db and later use to send notifications to that group. Subsequently whenever user logs in to some other device you can get the reg_id of that device and add it to the group. To add a device with the registration ID 51 to appUser-Chris, you would send this request:
{ "operation": "add", "notification_key_name": "appUser-Chris", "notification_key": "aUniqueKey", "registration_ids": ["51"] }
And when user logs out of the device you can remove the reg_id from the group.
Sending a notification to a group is same as sending notification to a registration id.
One issue I have observed here is if you fail to store the notification_key to your db while creating a group there is no way you can get it again. And a new group cannot be created for that same notification_key_name again. A workaround to this problem is choosing your notification_key_name wisely. So when you fail to store the notification_key and try again to create a group with the same noitification_key_name, you will get an error. In that case you can simply change the notification_key_name for that user and create a new group. Hope this helps.
i think you have to get Device IMEI no and save it to your server with Registration id and when user ReRegister him self with other device then get Check IMEI Number Replace it with old one. Main thing Rise now in old device check send IMEI no with message and IF IMEI no is Match then no issue otherwise block application.and inform user...
Thats it...