8

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?

Parichit
  • 257
  • 4
  • 13

3 Answers3

7

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);
Jeff Padgett
  • 2,380
  • 22
  • 34
  • you're missing a `.` after `admin.firestore()` and you're also missing a `)` at the end of your `.catch` statement. My code wouldn't work without it. – siefix Nov 08 '18 at 23:21
5

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.

Community
  • 1
  • 1
AL.
  • 36,815
  • 10
  • 142
  • 281
  • 2
    @AL.Could you let me know what you are using as a deviceId ? – sauvik Aug 24 '17 at 20:37
  • @amarok I have this problem as well. See this: for web - https://stackoverflow.com/a/10747/3114383 for android: https://developer.android.com/reference/android/telephony/TelephonyManager.html#getDeviceId or https://developer.android.com/reference/android/net/wifi/WifiInfo.html#getMacAddress – CantThinkOfAnything Jan 10 '18 at 13:19
  • That solution makes great sense, but what do you use for the deviceId? – Jeff Padgett Jun 22 '18 at 00:59
  • 1
    @JeffPadgett Right now, I still use [`ANDROID_ID`](https://stackoverflow.com/a/43176326/4625829), although it is no longer advisable for Android O and up. I have yet to find a really good replacement for it. – AL. Jun 22 '18 at 07:55
  • @amarok Sorry for not getting back so soon at this. I just noticed that your comment was almost a year ago, but to answer your question, see my comment above. Cheers! – AL. Jun 22 '18 at 07:56
  • 1
    Thanks, I am using ANDROID_ID as well. It is not advisable, but it seems to still work in this case. Android 8.0 is going to modify it and make it so ANDROID_ID is tied to the app signing Key and the Device Combination, which makes it safer for the user, but allows developer to get a Device ID that remains even across uninstall and reinstall. I think as long as we don't use it with 3rd parties, sell it, or use it for advertising tracking, it's ok. – Jeff Padgett Jun 23 '18 at 04:18
0

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?

DThink
  • 429
  • 2
  • 18