I think that instead of saving the token I get from onTokenRefresh() to another place, why not let Firebase keep it and then I query it directly by using this whenever needed:
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
This way, I should be able to get the token from the most reliable source.
But I tried to search it and did not find anything which suggests this use outside the service. I tried to implement it and found that it indeed returns the correct token if onTokenRefresh() is already called, otherwise it returns null. I think this will be the same wherever I manually save the token.
So is there any instruction which discourages the use in this manner?
You can safely call FirebaseInstanceId.getInstance().getToken() from anywhere and it will give you the current token.
The problem is that there are a few situations you won't catch if you only call it in your MainActivity:
the token may not have been created yet, in which return getToken() returns null.
the token may get recycled after you call getToken(), in which case your code is working with an outdated token.
To ensure you catch both these situations, you should implement FirebaseInstanceId.onTokenRefresh().
Related
My app sends notifications using Firebase Cloud Messaging FCM. For every user, I'm storing the device token in database and I fetch it when I want to notify him. I'm using FirebaseMessagingService with the overridden method onNewToken that updates my database with new tokens. I suppose that this method is called every 1 hour to check token's update, but I was expecting it to be also called when the service is initialized for the first time (after installing and running the app on device). However this is not the case. To remedy this, I could call onNewToken each time the user log in But I would like to know if this is an acceptable way or there is a better one.
To avoid abuse, I leave here extra information on my case :
I run my app on Android Studio emulator and I check the stored token in database, let's call it TOKEN-1.
Now I install the app on my phone and I show the token with String token = FirebaseInstanceId.getInstance().getToken(); Toast.makeText(MainActivity.this, token, Toast.LENGTH_LONG).show();
The token is different that the first one TOKEN-1, and TOKEN-1 is still stored in my database. This means that I can receive notifications only on emulator and not my phone.
Sorry for my long text and looking forward to reading your suggestions.
The FCM SDK and server work together to manage the token in the background, and listening to onNewToken ensures that you get notified when the token changes. For this reason you should always be listening to onNewToken, and update it in your own database whenever it changes.
There is no guarantee that your FCM token will be refreshed every hour (or even ever) though, as you seem to expect. Given the 1 hour interval, you might be thinking of Firebase Authentication ID tokens, which are short-lived and are indeed refreshed every hour.
Finally: the token doesn't get refreshed when you attach a listener. In fact: if the token was already generated before you attach a listener, your listener won't be called. For this reason, you'll typically also want to grab the current token in your main activity when the app starts, and store it in the database at that point.
This last code is mostly necessary during development, as that's where you're most likely to have the scenario where the token gets generated when you don't have an onNewToken listener yet. So instead of putting code in the main activity, you can also uninstall/reinstall the app after adding your onNewToken listener, as FCM will generate a new token upon installing the app in that case - and thus call your onNewToken with this initial token.
Firebase has deprecated some of the messaging calls in the com.google.firebase:firebase-messaging:17.1.0 release. This post goes over those changes nicely.
Question: Can someone tell me if is it considered bad practice to not implement onNewToken and instead just call the below block each app launch This probably seems excessive to Android folks but feels like home from an iOS point of view.
FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener(this) { instanceIdResult ->
// Just use this call
val newToken = instanceIdResult.token
Log.i("newToken", newToken)
}
#Override
public void onNewToken(String s) {
super.onNewToken(s);
// Leave this unimplemented
}
I am more familiar with iOS which calls its onNewToken equivalent on every app launch. So for iOS I put logic there to determine if my backend needs to be updated.
getInstanceId() docs say This generates an Instance ID if it does not exist yet, which starts periodically sending information to the Firebase backend. This makes me assume I can just call FirebaseInstanceId.getInstance().instanceId.addOnSuccessListener each launch.
Something very important that no one has mentioned yet:
If you check for the current device token only after an app launch, you might loose the event of the token being updated while your app is in the background (of course) and you won't be able to receive remote push messages from your server until the user launches the app again and you send the new token to the server.
The whole purpose of having that callback which can also be called while your app is in the background is to prevent loosing backend messages (important if your app or some important features of it relies a lot on push notifications). It is important to be aware that this callback will not only deliver you the token when you register the device for the first time but also: Called if InstanceID token is updated. This may occur if the security of the previous token had been compromised.
So:
Can someone tell me if is it considered bad practice to not implement onNewToken and instead just call the below block each app launch This probably seems excessive to Android folks but feels like home from an iOS point of view.
Yes, it is actually a bad practice to not implement onNewToken().
First of all, I'm highly skeptical of any logic that suggests that if something is OK in iOS, that it would be OK on Android!
The implementation of push messaging between Android and iOS is extremely different. On Android, it's dependent on Play Services, which runs in another process. On iOS, it's something completely different. The rules of engagement are simply not at all the same.
Note that the suggested token retrieval method is via callback. That is suggesting that token generation is essentially asynchronous. In other words, at app launch (in whatever way you actually define that), the background stuff that manages to token might not be done with that yet. There simply might not be any token available when you ask for it. And who knows how long that takes? You're better off just accepting the token when the system tells you it's ready instead of making a guess about when it's ready. Follow the recommended implementation path.
Despite the documentation says that onNewToken is called upon the first app start, it is not. That is why I use FirebaseInstanceId respectively getToken() when I need the Id while onNewToken has not been called before although the app is already running for a while. (So we do both in our project)
What I observe is that Firebase will call onNewToken shortly after I was fetching the token via FirebaseInstanceId. It seems that fetching the token this way initiates something within the Firebase service.
However, it works that way and that is good enough for our current project.
Edit: As getToken() of FirebaseInstanceId got deprecated recently, please refer to Arthur Thompson's answer.
Calling FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener on every app launch is an option (an unnecessary option), onNewToken is there specifically to provide you access to the token when available.
Note that calling FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener on every app launch would require you to handle the case when the token is not yet available, using onNewToken avoids that.
Let's simplify this equation - you should do both.
In our app we were not manually checking FirebaseMessaging.getInstance().getToken(), but did have the onNewToken override set up. This worked for most users, but we had instances of people who would stop receiving notifications unless they reinstalled the app.
So if you really want to be sure you always have an up to date token, you need to do both here. Check at startup with getToken() to make sure you didn't miss an update, and subscribe to onNewToken to get notified if it changes in between app launches. Firebase documentation does allude to this, although to be honest it could be more clear:
The onNewToken() function works like it's predecessor, onTokenRefresh().
Implementing the first block on your post requests and waits for a token. This guarantees that a token (or an exception) would return. However, that token isn't guaranteed to stay the same forever. Similar with onTokenRefresh(), onNewToken() is triggered when a token for the corresponding app instance is generated, which in turn you should use and replace with the old token you got.
My answer here has more details.
From where and when is the token retrieved and when is it available?
Is it a synchronous call to the Firebase server? If I call it too soon in the app lifecycle, might it not have been populated yet?
From where and when is the token retrieved and when is it available?
The token is generated by the FCM Instance ID service in the background, which starts as soon as your app runs. The details on how the token gets generated is unclear, but how I see it is that the device needs a decent connection to the internet in order for it to communicate with the FCM servers for the token.
Is it a synchronous call to the Firebase server?
Technically speaking, no. As mentioned in the docs:
FirebaseInstanceID.getToken() returns null if the token has not yet been generated.
At this time, if the token is null, you should expect a trigger in your onNewToken() where you could then call getToken() which should now contain the token.
If I call it too soon in the app lifecycle, might it not have been populated yet?
It's usually okay to call getToken() as soon as possible -- in your app's MainActivity -- in most cases, by the time your app reaches that point, it already has a value. But then again, you should still handle it properly if it is null.
As I read from documentation and other sources it is advised to call FirebaseInstanceId.getInstance().getToken(); inside onTokenRefresh() to make sure that we get the updated token
what I tried was to call it every time the app is opened and its returning value always, and I am wondering is that a thing to trust ?(is it guaranteed to generate the token every time its called? ) or is there any case it might return null ?
since I don't want users logged in to multiple devices with the same account I am hoping to use it as unique identifier.
As per my knowledge and experience I will try to resolve your doubts:
Is it guaranteed to generate the token every time its called?
No it will not generate token every time you call this method. It will just return current token.
Is there any case it might return null ?
Yes it returns null if your token is not yet generated.
I am hoping to use it as unique identifier
Yes you can use it as a unique identifier for each device not for each account. Every device has its unique token.
This is from the official doc:
Retrieve the current registration token
When you need to retrieve the current token, call
FirebaseInstanceId.getInstance().getToken(). This method returns null
if the token has not yet been generated.
For more information refer this link.
Our business logic requires us to instantiate Firebase during runtime. We fetch the Firebase credentials (App ID, API key etc.) from a default Firebase location after knowing the user's public key and create a Firebase instance using those credentials.
This means that there are two Firebase instances used within the app:
The default "index" Firebase that gives us credentials for the
second
The actual Firebase that we intend to use from that point
forward
The second Firebase is initialised like this:
FirebaseApp app = FirebaseApp.initializeApp(<context>, <options>, <app_label>);
Our problem is that the traditional method of retrieving the FCM token using the FirebaseInstanceIdService and onTokenRefresh() fails since the onTokenRefresh() method is called only by the first Firebase instance.
Directly calling String token = FirebaseInstanceId.getInstance(app).getToken(); returns null as it is never ready when called. We have even tried polling this to test if a token is generated at some point. No luck.
How can we generate an FCM token reliably from a Firebase instance instantiated during runtime?
In general, only the traditional method of getting the token (getToken()) is the only one that refers to the main Firebase Instance. This can be called almost anywhere in your app (but is often called in the initial activity). This will return the token associated to the Sender ID that is seen in your google-services.json. It is also possible for this to return null if the token is still being generated. In cases like that, onTokenRefresh() is triggered upon generation.
However, if you intend to generate a FCM registration token for a different Firebase Project, you'll have to make use FirebaseIntsanceId.getInstance.getToken(String authorizedEntity, String scope), where authorizedEntity is the Sender ID of that different Firebase Project and scope is "FCM" (you can use FirebaseMessaging.INSTANCE_ID_SCOPE as default).
In short, you don't need to specify the Firebase Instance when generating a token.
Also see my answers here and here.