22

From the official documentation I understand that the way it works is something like this:

  1. User installs app, FCM token is generated
  2. Sending token to app server
  3. Server uses token to send push-notifications to this device.

What if at the same time this user installs app on the other device - should I store multiple tokens per user on the app server? If yes - that means there should be something like checking for which ones are expired?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Graykos
  • 1,281
  • 3
  • 20
  • 31
  • 1
    Each time you fire a push request, you can get a json structure back. Example: `{"multicast_id":5221242244170421431,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1524496848239873%cc9b4facf9fd7ecd"}]}` -- if failure equals 1, just delete the corresponding token. – Andres SK Apr 23 '18 at 19:10
  • 1
    Well you can store smth like Unique Device Id and Token. Make the two unique constraint. Setup a mechanism to delete records older than x time. – ErvTheDev Oct 22 '18 at 10:12

5 Answers5

12

I also came across the exact challenge and had to resolve to a solution: Storing each token for the user against the device id. It's interesting enough to know that this function in fact exists in the firebase messaging method. But more surprising is the fact that there's no documentation to handle such scenario. https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceId.html#getId()

Summarily, while sending the new token to the server, also send along the device id returned by the getId() method and use it to enforce uniqueness of token per device.

And to also apply this solution while taking advantage of device grouping feature of FCM, you can make a server request on the FCM group, in order to delete the old token on that device-id before replacing it with the new.

tormuto
  • 587
  • 5
  • 16
  • But the documentation for getId() says: `Returns a stable identifier that uniquely identifies the app instance.` . It doesn't identify the device id. – petemir Apr 17 '18 at 18:08
  • 1
    https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceId Yes, but that doesn't change unless: Instance ID is stable except when: *App deletes Instance ID. *App is restored on a new device. *User uninstalls/reinstall the app. *User clears app data. – tormuto Apr 19 '18 at 16:54
  • 14
    I know, I read that, but you are calling it a device id, it isn't that, it's just an instance id. If user deletes and reinstalls the app, you will have another instance id, in the same device. Thus, you won't be able to difference an old token from a new one for the same device. – petemir Apr 19 '18 at 18:20
5

What if at the same time this user installs app on the other device - should I store multiple tokens per user on the app server?

Yes. A user could have multiple devices, a case where Device Groups are commonly used.

If yes - that means there should be something like checking for which ones are expired?

If a token expires, a callback is triggered (onTokenRefresh() for Android), from where you'll have to send the new token to your App Server and delete the old one corresponding to the user/device.

AL.
  • 36,815
  • 10
  • 142
  • 281
  • But what if user logged in from 10 devices and lost 9 of them. So they won't fire up a callback. Will I have to send notifications to these lost devices forever? – Graykos Jun 12 '17 at 10:57
  • 1
    It's the developer's responsibility to keep track of the mapping for device groups. See my answers [here](https://stackoverflow.com/a/42248240/4625829) and [here](https://stackoverflow.com/a/42522002/4625829). – AL. Jun 13 '17 at 01:49
  • 10
    "delete the old one corresponding to the user/device"... Ok, but how do I determine the unique device? What if it's the same user using 2 different browsers or different machines. Is there a firebase method to get a unique device id, or a public web API for that? – johnb003 Sep 27 '17 at 00:20
  • @johnb003 when you send a notification to a group, you get success, partial success or failure. If you get partial success or failure it also includes all tokens that cannot receive the notifications, so you get those tokens and remove them from the group? Idk. Couldn't find anything useful online or in the documentation... https://firebase.google.com/docs/cloud-messaging/android/device-group#device-group-http-response – kironet Nov 15 '19 at 11:23
  • Thanks @AL. very useful information. An idea just came into my mind: why not use message topics with user id prefix? in this way you don't send notifications to tokens, but to topics composed by user_id + topic name. Once the user logs in, you register the device token (if updated) and link the token to the respective topic name (eg. 123_new-order-registration). Maybe there are better ways to do this, but you got the idea – funder7 Nov 23 '20 at 13:56
  • There's also a `send` with dryRun option that allows to emulate a send message without delivering it, it could be used to check if a device is reachable: https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/messaging/FirebaseMessaging#public-string-send-message-message,-boolean-dryrun – funder7 Nov 23 '20 at 14:02
  • @funder7 For the [topic use-case](https://stackoverflow.com/a/42291098/4625829) you mentioned. For dry run, yup that could work, depends on how you use it I guess. – AL. Nov 23 '20 at 15:27
  • @AL. Ouch! ....What about user groups? I have to read more documentation to understand how they work, but they seem the right way of handling users with many devices. Maybe they're private, and more suited for this purpose. Btw I'd like to avoid any hacky solution if possible... fcm is powerful but needs some time for learning how it works. I think that it's intended to leave some degree of choice to the user, on how to handle the token lifecycle. – funder7 Nov 23 '20 at 16:26
  • @funder7 I assume you meant _device groups_ -- yes, handling multiple devices for a single user is the main use case device groups was meant to solve. It does need handling from the developers to make it work effectively. I have these answer ([here](https://stackoverflow.com/a/42522002/4625829) and [here](https://stackoverflow.com/a/42248240/4625829)) that might be able to help you out initially. Cheers! – AL. Nov 24 '20 at 04:29
  • yes I meant device groups sorry, thanks for the links, it would be nice to collect all that information in a single document. It's a topic with many variables to consider in order to do a complete analysis...especially when you need to handle access from web & mobile apps in the same application! I'm aiming for a solution that can be mostly server-based, letting the client just ask for the token to firebase (and listen for incoming messages) – funder7 Nov 24 '20 at 13:25
  • Good idea to handle multiple device tokens!!! Just to add user's device tokens to a topic, so you don't need to check if tokens expired or not. – Shawn Lee Feb 03 '21 at 09:29
3

Graykos, You can do sth like this:

Each time a user have a new login, get a new token from google, and when logout delete that token (Edit note: Close app & run it again, not called a new login and it's not create a new token). So if user login from multiple device/browser OR multiple user login from one device/browser one after the other, you can nicely handle all of that.

in this way "multiple user login from one device/browser one after the other", all of them have the same token (so delete and renew for each login)

As Andres SK mentioned in the first comment of your question, you can delete the token when failure happen (maybe the lost device that user cannot logout from).

Ali_Ai_Dev
  • 514
  • 7
  • 16
  • that won't work if the user deletes the app without performing the logout operation before. – Fernando Rocha Sep 26 '19 at 22:56
  • you didn't read the two last line in answer. If the user uninstall the App without logout from the account. when you want to sent a notification you get failure and you must to delete that token from the server. – Ali_Ai_Dev Sep 30 '19 at 12:50
  • 1
    Delete the token once the user logs out. Wait, so you never send a notification to the user just because the user logged out. – DroidDev Aug 28 '20 at 20:09
  • 1
    [DroidDev](https://stackoverflow.com/users/6641282/droiddev) yeah exactly, like any app for example Gmail when you logout form your account, you will never receive a mail notification and when you login again receive a new code. Read a "error:NotRegistered" in FCM (https://firebase.google.com/docs/cloud-messaging/http-server-ref#error-codes) – Ali_Ai_Dev Aug 30 '20 at 20:21
  • It can be a solution, maybe it's the only one, but honestly I don't like it 100%, there are many things that can go wrong. 1) you don't know if a message can be delivered until you send a message; 2) If for sudden you send a reconciliation message, and google, or your backend fails, then the process will not work as expected – funder7 Nov 23 '20 at 13:43
  • @funder7 Your right, but for every service you write down, we have these two problems. If a user delete app or reset factory etc... how we wanna detect that be for send? – Ali_Ai_Dev Jan 31 '21 at 21:39
1

I have a similar situation and found out the following error response code when trying to send to an expired token:

{
"error": {
    "code": 404,
    "message": "Requested entity was not found.",
    "status": "NOT_FOUND",
    "details": [
        {
            "@type": "type.googleapis.com/google.firebase.fcm.v1.FcmError",
            "errorCode": "UNREGISTERED"
        }
    ]
}}

My app server accepts multiple tokens per user assuming they use multiple devices. When sending a new message, I will try to send it to all tokens related to the user. Those that return this error will be deleted so future message will only be sent to active tokens.

salvadorC
  • 111
  • 3
1

I came across the same problem, How I tried to solve this is to maintain a data structure like this. And save this data to my notifier server's database.

type UserToken struct {
    UserId      string
    Tokens      []DeviceToken
}

type DeviceToken struct {
    DeviceName  string
    Token       string
}

Whenever you get a new device token from firebase just replace the existing one with new one for that device.

And for the web part I think storing the last one is enough.

Abdul Momen
  • 379
  • 2
  • 13