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.
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.
I am working on one android app And the problem is when a token is expired, then I need to logout the user. Right now I need to check the token expire in all my Fragment. So Is there any way so that I can do this task from centralized locations.
Are you using retrofit? If so, you could add a custom Interceptor, which intercepts every response and handles 401 by redirecting to login.
You could also define a SessionHandler, that requires a context and from the Interceptor you would call sessionHandler.onSessionExpired() for example. Hope you get the idea
Personally, I prefer to use pub/sub pattern for these kind of problems. In general, when you find out that your token has been expired (for example in an interceptor) raise an event (named session expired). Also, there are some consumers (for example a BaseActivity which all other activities extended it) that capture this event and react to it (according to your question, redirect to the LoginActivity). To implement this approach, you can both use some ready solution (like greenrobot) or develop it by using LiveData.
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.
I have a REST API secured with JWT. The client is an Android app and a web app. Android app gets a new token when a user login, after that it works with that token. However the token will expire in 60 minutes, so I have to refresh it. I know there are 3 approaches for this.
issue a fresh token in every request
issue a fresh token when the current one is close to expire. e.g. 10
min
let client app request a new token when it needs it using a "refresh
service" of your api.
Please consider the following...
I am not happy about the first suggestion above
I did try the third suggestion. However in my android app I got like 60 REST calls. Then what I should be doing is with every REST call (ex: getUsers()), I first have to check whether the token is about to expire and if yes, get a new token from REST API (That means I have to run another REST call to the refresh() method in API). After this check and getting the new token I can execute the getUsers() method. The problem here is that every REST call should run inside the onResponse() method of my refresh() method (I am using Retrofit) and that is simply not possible as I have to then duplicate the same method for 60 times with 60 names. I am sure the same issue will arise with the web app as well.
Due to the above reasons I am considering the 2nd suggestions in my first list, renewing the token at the server it self. If the token is "valid" and if it is "about to expire" I will refresh it from server and will send to app as a header.
I need to know whether that method is an industry practicing method and whather it is the best choice. If not, how I can proceed with the 3rd suggestion in my first list.
I have recently implemented the similar setup with JWT in one of my Android apps. I don't know if my suggestion will help you, but it might give you an insight on how others are doing it.
Option 1: This is very much redundant, and it violates the sole purpose of using JWT in the first place. If I get a new JWT on every request, I can use that for further requests, thus practically have a token with no expiration.
Option 2: This requires server side extra operation, plus this is not feasible. How do you plan to detect which tokens are already distributed and "about to expire" so that you can renew them? Tons of users will have tons of tokens and if you plan to save JWT in database, then it would get too messy.
Option 3: This method is too redudndant and requires client side operation on checking whether token is about to expire and calling refresh service based on that. I always prefer lesser operation to do more work.
What I did, is that, I used two tokens for the system. One as a Refresh Token (token used to request JWT refresh) and one as JWT (for every request validation).
With successful login, received a refresh token and saved it locally in app (SharedPref). This token is also preserved in database too. Next, requested and validated JWT with that with that refresh token. From now onwards, every request contains this JWT in header. I also request new JWT everytime when my app is opened, i.e, in my Splash page.
Now, if any request contains expired JWT, simply return a common response like "Session expired" with a fixed status code. If any HTTP request has this specific status code in its response, I requested another API call with the refresh token to get fresh JWT for future requests.
As I needed to add this check (whether contains "Session Expired") in every HTTP response, I wrote a common function and passed the HTTP responses via that method, so that, I do not need to rewrite or copy-paste every bit of it.
This requires very less code and minimal operation on both server and app end. My system has a JWT expiration time of 20 minutes and I haven't faced any problem till now. Worst what happens is, in a single request, user receives token expired, gets token, and recalls that function. This results in a slightly delayed operation for users that is already in my app continuously for more than 20 minutes. If any user is in my app for that long, it's a good problem to have, right? :)
It's not clear whether the users have to log-in in your app every time or session is held so that logged-in users can directly enter the app. If session is hold, just keep the refreshToken in app locally (SharedPref) and use that to get JWT every time the app is opened. If any user is misusing your API (scraping or for any other purpose), simply move that specific refresh token to a black-list so that this user doesn't get new JWT.
Please let me know if anything's not clear. Thanks.
for my first question on StackOverflow I'm gonna ask about Google Cloud Messaging service, and in particular Loopback's implementation.
So, I'm developing an app and started to work on a different branch to introduce Loopback's push notification handling and it's various tools for REST Api. Even if this topic is gonna cover strictly Loopback's way to handle GCM, the question is also related to the original way as is described on Google docs.
So, the main idea behind GCM's kick-off is to check whether the device is registered or not.
This is done by a simple check on a SharedPreferences variable, a name used to store our RegistrationID value.
final LocalInstallation installation = new LocalInstallation(context, adapter);
If this is found, the device has to notify the server, communicating the token.
Else, a registration to GCM has to be done.
Once this is done, the device notifies the server. ( registerInBackground(installation) will eventually call saveInstallation(installation) after retrieving RegistrationId )
if (installation.getDeviceToken() != null) {
saveInstallation(installation);
} else {
registerInBackground(installation);
}
If communication is successful, the device saves RegistrationId using SharedPreferences as described above. (NOTE : getDeviceToken() is Loopback's way to handle via API the value in SharedPreferences)
Let's say this "GCM-Check" is being done every time my MainActivity is being created (so, during the onCreate method).
We also know GCM is sometimes messy, and wants to refresh my app's RegistrationId or some other stuff that, to be honest, is not completely clear to me right now. In short terms, GCM invalidates the token of my app. This causes an error-message when the server send a push-notification using the Token bound to my device-app.
An error similar to
{"multicast_id":0123456789012345678,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"NotRegistered"}]}
You can see, "failure":1 and "results":[{"error":"NotRegistered"}]
Loopback reacts just as Google docs say, by having the server remove the record of the device linked to the faulty RegistrationId .
Comprehensible.
Back to our device. Launching my app again and loading MainActivity, causes the same "GCM-check" procedure. This time the app can find RegistrationId using SharedPreferences, and can directly notify the server, which creates a record with the given RegistrationId.
No new registration is being handled by the device-app.
You can see the loop in here. Device will have no knowledge of it's token invalidity and will continue to tell the server the same data, which will keep sending information to the wrong registrationId, thus removing it after receiving the related error.
The problem is that the app has to rely on data which is created once and never gets modified. To remove the old data I should send a notification to the device, which is obviously not possible as I can't reach it from GCM. Other solutions possible is notify the user by sending an email or sms, and ask him to click a button for example, but I'd rather have a more "automated" approach to the problem.
A VERY BAD SOLUTION I'VE FOUND
As to my knowledge the only error-info is returned from GCM to the server during a push-notification, I've made a little hack on the device.
The idea is simple: create a POST request to GCM Servers, using the headers my server should use to authenticate. This causes the error to be given to the device itself, which can parse the JSON and notice what happened. From here the app can forge a new registration process, fixing the issue.
What is bad about this? The fact that to authenticate the device as the server, I have to hard-code the ServerKey and distribute it in every app. The ServerKey should be used only on the server, making this solution a very bad idea.
This being said, the idea of simply letting the device know its state using a SharedPreference value is not so great, as it would only tell me if I ever registered at least once, without letting me know my current status.
On other apps I've developed which use GCM just as well, I've solved in different ways, like having a Button to be clicked by the user or reading some specials SMS send by the server, which then enable GoogleCloudMessaging.unregister() at first and eventually GoogleCloudMessaging.register()
So, asking for forgiveness for such a wall-of-text, how have you solved this NotRegistered thing?
Thanks for your effort and time in reading and, hopefully, in answering : )
As an addendum to my comments, since it helped and I have more space here:
Instead of just checking whether or not the token exists inside your SharedPreferences, you should also check to see if the version of your app that token is for matches the version that is currently running the check (that was a suggestion from Google's docs).
If the device version do not match, you should request a valid token (which could actually be the same, but is not guaranteed). Now, you may also want to check for the PACKAGE_REPLACED broadcast (in case you're not incrementing the version in your manifest each time you install for tests, which I'm very guilty of) which, if triggered, should also force you to request a new token.
As for why there's sometimes a change in the token and sometimes not: I couldn't agree more that it's totally sporadic and can't help feeling like there's something going on we don't really know about.
Sometimes the new request after PACKAGE_REPLACED returns the same key; sometimes it doesn't. What happens in between those that makes it weird? God, I would LOVE to know, and I wish I had more info on it. But that's part of why I mentioned trying to catch that broadcast: forcing the check when it happens should ensure that you're always getting a new valid one (in the event that the version check passes when it shouldn't) if it's available.
Hopefully this helps~