I am using Firebase FCM to send notifications from my server to the users.
When the user installs the App for the first time, i catch the fresh and new token at MessagingService.java:
#Override
public void onNewToken(#NonNull String tkn) {
super.onNewToken(tkn);
sendTokenToServer(tkn);
}
Here comes the problem, when the user closes session (without uninstalling the app), SharedPreferences are deleted. A new session is started; but onNewToken() is not called. So, i must manually retrieve the Token inside my MainActivity in order to send it to the server. I am getting the updated token with this piece of code:
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(MainActivity.this, new OnSuccessListener<InstanceIdResult>() {
#Override
public void onSuccess(InstanceIdResult instanceIdResult) {
sendTokenToServer(instanceIdResult.getToken());
}
});
As you know, that code is deprecated and should be avoided. Instead, i tried to replace it with this piece of code with no success:
FirebaseInstallations.getInstance().getToken(true).addOnCompleteListener(new OnCompleteListener<InstallationTokenResult>() {
#Override
public void onComplete(#NonNull Task<InstallationTokenResult> task) {
if(task.isSuccessful()) {
String token = task.getResult().getToken();
}
}
});
The Token length obtained at onNewToken() is 163.
The token length obtained at deprecated call is 163 (perfect, but deprtecated).
The token length obtained at FirebaseInstallations is 316.
My firebase API at server side fails to send a notification using the code of 316 length.
Any one knows what i am doing wrong? Or why i get those different length tokens?
Update:
Server side python, retrieves token from database and sends the notification like this. Please note this code is working when token len is 163.
from pyfcm import FCMNotification
push_service = FCMNotification(api_key=" ... ")
push_service.notify_single_device(registration_id=token, data_message=datamessage, time_to_live=1296000)
When trying to send a notification with long token this is the message I get:
{'multicast_ids': [8149274412512777031], 'success': 0, 'failure': 1, 'canonical_ids': 0, 'results': [{'error': 'InvalidRegistration'}], 'topic_message_id': None}
From the documentation for FirebaseInstanceId:
This class is deprecated. Firebase Instance ID has been replaced with
FirebaseInstallations for app instance identifiers and
FirebaseMessaging.getToken() for FCM registration tokens.
Looks like you need FirebaseMessaging.getToken() not FirebaseInstallations.getInstance().getToken(true) as you want a FCM registration token.
These APIs provide different tokens for different purposes.
So in your example it would be:
FirebaseMessaging.getInstance()
.getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
#Override public void onComplete(#NonNull Task<String> token) {
}
}
);
In my toy chat application, I would like to set up a way to send notifications to users whenever other users send messages using Firebase Google Cloud Messaging. In my approach, I intend to capture the devices' registration tokens, and then later send notifications to those devices using Google’s Cloud Functions. Also, my application requires that users be authenticated.
To capture the tokens in my Firebase Realtime Database, I have sub-classed FirebaseInstanceIdService as follows:
public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService {
private static final String TAG = "MyFirebaseInstanceIdService";
private static final String FCM_REG_TOKENS = "fcm_registration_tokens";
#Override
public void onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "This device updated token: " + refreshedToken);
sendRegistrationTokenToServer(refreshedToken);
}
private void sendRegistrationTokenToServer(final String refreshedToken) {
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference();
dbRef.child(FCM_REG_TOKENS).push().setValue(refreshedToken);
Log.d(TAG, "sendRegistrationTokenToServer: NEW TOKEN: " + refreshedToken );
}
}
However, it appears that the above service runs immediately the application is launched, even before the SignInActivity is run. And, at this point, obviously I am yet to capture the details of the Firebase user to store the token in the correct database.
My gut feeling is that I am doing this incorrectly. What is the proper way to capture device registration tokens in order to send notifications to devices with those tokens?
FCM tokens are tied to an installed app (that's why they're called Instance ID Tokens), and not necessarily to a specific Firebase Authentication user (since the app may not even require the user to sign in).
Your code is the correct way to capture the token for the app instance.
If you have a requirement to associate the token with a user, then you'll need some code to check both values. A simple way to do this is to get the current user from Firebase Auth in your service:
#Override
public void onTokenRefresh() {
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "This device updated token: " + refreshedToken);
sendRegistrationTokenToServer(refreshedToken);
}
private void sendRegistrationTokenToServer(final String refreshedToken) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference();
dbRef.child(FCM_REG_TOKENS).child(user.getUid()).setValue(refreshedToken);
Log.d(TAG, "sendRegistrationTokenToServer: NEW TOKEN: " + refreshedToken );
}
}
This keeps a single token for each unique UID. Note that the correct data structure here depends on your actual use-case, since the same user may use your app on multiple devices and thus have multiple tokens.
How do I handle situation, when user logs out of my application and I no longer want him to receive notifications to the device.
I tried
FirebaseInstanceId.getInstance().deleteToken(FirebaseInstanceId.getInstance().getId(), FirebaseMessaging.INSTANCE_ID_SCOPE)
But I still receive the notifications to my device's registration_id.
I also made sure that this is the token I should delete:
FirebaseInstanceId.getInstance().getToken(FirebaseInstanceId.getInstance().getId(), FirebaseMessaging.INSTANCE_ID_SCOPE)
or simply FirebaseInstanceId.getInstance().getToken()).
I also tried FirebaseInstanceId.getInstance().deleteInstanceId(), but then the next time I call FirebaseInstanceId.getInstance.getToken I receive null (it works on the second try).
I guess, after deleteInstanceId I could immediately call getToken() again, but it looks like a hack. And also there's this answer which states that it shouldn't be done, but it proposes deleting the token which apparently doesn't work.
So what is the right method to handle this?
Okay. So I managed to do some testing and have concluded the following:
deleteToken() is the counterpart of getToken(String, String), but not for getToken().
It only works if the Sender ID you are passing is a different Sender ID (not the same ID that can be seen in your google-services.json). For example, you want to allow a different Server to send to your app, you call getToken("THEIR_SENDER_ID", "FCM") to give them authorization to send to your app. This will return a different registration token that corresponds only to that specific sender.
In the future, if you chose to remove their authorization to send to your app, you'll then have to make use of deleteToken("THEIR_SENDER_ID", "FCM"). This will invalidate the corresponding token, and when the Sender attempts to send a message, as the intended behavior, they will receive a NotRegistered error.
In order to delete the token for your own Sender, the correct handling is to use deleteInstanceId().
Special mentioning this answer by #Prince, specifically the code sample for helping me with this.
As #MichałK already doing in his post, after calling the deleteInstanceId(), getToken() should be called in order to send a request for a new token. However, you don't have to call it the second time. So long as onTokenRefresh() onNewToken() is implemented, it should automatically trigger providing you the new token.
For short, deleteInstanceId() > getToken() > check onTokenRefresh() onNewToken().
Note: Calling deleteInstanceId() will not only delete the token for your own app. It will delete all topic subscriptions and all other tokens associated with the app instance.
Are you positive you're calling deleteToken() properly? The value for audience should be (also seen from my answer that you linked) is "set to the app server's sender ID". You're passing the getId() value which is not the same as the Sender ID (it contains the app instance id value). Also, how are you sending the message (App Server or Notifications Console)?
getToken() and getToken(String, String) returns different tokens. See my answer here.
I also tried FirebaseInstanceId.getInstance().deleteInstanceId(), but then the next time I call FirebaseInstanceId.getInstance.getToken I receive null (it works on the second try).
It's probably because the first time you're calling the getToken(), it's still being generated. It's just the intended behavior.
I guess, after deleteInstanceId I could immediately call getToken() again, but it looks like a hack.
Not really. It's how you'll get the new generated (provided that it is already generated) token. So I think it's fine.
I did a brief research on what would be the most elegant solution to get back the full control (subscribe and unsubscribe to FCM) as before. Enable and disable the FCM after the user logged in or out.
Step 1. - Prevent auto initialization
Firebase now handle the InstanceID and everything else which need to generate a registration token. First of all you need to prevent auto initialization. Based on the official set-up documentation you need to add these meta-data values to your AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<application>
<!-- FCM: Disable auto-init -->
<meta-data android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<meta-data android:name="firebase_analytics_collection_enabled"
android:value="false" />
<!-- FCM: Receive token and messages -->
<service android:name=".FCMService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
</application>
Now you disabled the automatic token request process. At the same time you have an option to enable it again at runtime by code.
Step 2. - Implement enableFCM() and disableFCM() functions
If you enable the auto initialization again then you received a new token immediately, so this is a perfect way to implement the enableFCM() method.
All subscribe information assigned to InstanceID, so when you delete it then initiate to unsubscribe all topic. On this way you able to implement disableFCM() method, just turn back off auto-init before you delete it.
public class FCMHandler {
public void enableFCM(){
// Enable FCM via enable Auto-init service which generate new token and receive in FCMService
FirebaseMessaging.getInstance().setAutoInitEnabled(true);
}
public void disableFCM(){
// Disable auto init
FirebaseMessaging.getInstance().setAutoInitEnabled(false);
new Thread(() -> {
try {
// Remove InstanceID initiate to unsubscribe all topic
// TODO: May be a better way to use FirebaseMessaging.getInstance().unsubscribeFromTopic()
FirebaseInstanceId.getInstance().deleteInstanceId();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
Step 3. - FCMService implementation - token and message receiving
In the last step you need to receive the new token and send direct to your server.
Other hand you'll receive your data message and just do it what you want.
public class FCMService extends FirebaseMessagingService {
#Override
public void onNewToken(String token) {
super.onNewToken(token);
// TODO: send your new token to the server
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
String from = remoteMessage.getFrom();
Map data = remoteMessage.getData();
if (data != null) {
// TODO: handle your message and data
sendMessageNotification(message, messageId);
}
}
private void sendMessageNotification(String msg, long messageId) {
// TODO: show notification using NotificationCompat
}
}
I think this solution is clear, simple and transparent. I tested in a production environment and it's works. I hope it was helpful.
I was working on the same problem, when I had done my logout() from my application. But the problem was that after logging out, I was still getting push notifications from Firebase. I tried to delete the Firebase token. But after deleting the token in my logout() method, it is null when I query for it in my login() method. After working 2 days I finally got a solution.
In your logout() method, delete the Firebase token in the background because you can not delete Firebase token from the main thread
new AsyncTask<Void,Void,Void>() {
#Override
protected Void doInBackground(Void... params) {
try
{
FirebaseInstanceId.getInstance().deleteInstanceId();
} catch (IOException e)
{
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
// Call your Activity where you want to land after log out
}
}.execute();
In your login() method, generate the Firebase token again.
new AsyncTask<Void,Void,Void>() {
#Override
protected Void doInBackground(Void... params) {
String token = FirebaseInstanceId.getInstance().getToken();
// Used to get firebase token until its null so it will save you from null pointer exeption
while(token == null) {
token = FirebaseInstanceId.getInstance().getToken();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
}
}.execute();
Developers should never unregister the client app as a mechanism for
logout or for switching between users, for the following reasons:
A registration token isn't associated with a particular logged in user. If the client app unregisters and then re-registers, the app can
receive the same registration token or a different registration token.
Unregistration and re-registration may each take up to five minutes to propagate. During this time messages may be rejected due to the
unregistered state, and messages may go to the wrong user. To make
sure that messages go to the intended user:
The app server can maintain a mapping between the current user and the registration token.
The client app can then check to ensure that messages it receives match the logged in user.
this quote is from a deprecated google documentation
But there is reasons to believe this is still true - even if the documentation above is deprecated.
You can observe this here - check out how they do it in this codelab https://github.com/firebase/functions-samples/blob/master/fcm-notifications/functions/index.js
and here
https://github.com/firebase/friendlychat-web/blob/master/cloud-functions/public/scripts/main.js
Since the getToken() is deprecated, use getInstanceId() to regenerate new token instead. It has same effect.
public static void resetInstanceId() {
new Thread(new Runnable() {
#Override
public void run() {
try {
FirebaseInstanceId.getInstance().deleteInstanceId();
FirebaseInstanceId.getInstance().getInstanceId();
Helper.log(TAG, "InstanceId removed and regenerated.");
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Use this methods.
This is my solution, and I referred this at here
When you sign-up, use initFirebaseMessage,. and when log-out or delete
use removeFirebaseMessage().
private fun removeFirebaseMessage(){
CoroutineScope(Dispatchers.Default).launch {
FirebaseMessaging.getInstance().isAutoInitEnabled = false
FirebaseInstallations.getInstance().delete()
FirebaseMessaging.getInstance().deleteToken()
}
}
private fun initFirebaseMessage(){
val fcm = FirebaseMessaging.getInstance()
fcm.isAutoInitEnabled = true
fcm.subscribeToTopic("all")
fcm.subscribeToTopic("")
}
Another handy way to clear the firebase token and regenerated a new one using FirebaseMessaging.getInstance()
fun clearFirebaseToken() {
FirebaseMessaging.getInstance().apply {
deleteToken().addOnCompleteListener { it ->
Log.d("TAG++", "firebase token deleted ${it.result}")
token.addOnCompleteListener {
Log.d("TAG++", "firebase token generated ${it.result}")
if (it.result != null) saveTokenGenerated(it.result!!)
}
}
}
}
Just call deleteToken method on a background Thread upon Logout:
https://firebase.google.com/docs/reference/android/com/google/firebase/iid/FirebaseInstanceId.html#public-void-deletetoken-string-senderid,-string-scope
FirebaseInstanceId.getInstance().deleteToken(getString(R.string.gcm_defaultSenderId), "FCM")
The first argument takes the SenderID as it is defined in your FireBaseConsole
It takes a few seconds to update - and after that, you will no longer get FCM notifications.
I know I am late for the party. deleteInstanceId() should be called from the background thread since it's a blocking call. Just check the method deleteInstanceId() in FirebaseInstanceId() class.
#WorkerThread
public void deleteInstanceId() throws IOException {
if (Looper.getMainLooper() == Looper.myLooper()) {
throw new IOException("MAIN_THREAD");
} else {
String var1 = zzh();
this.zza(this.zzal.deleteInstanceId(var1));
this.zzl();
}
}
You can start an IntentService to delete the instance id and the data associated with it.
The firebase.iid package that contains FirebaseInstanceId is now deprecated. Auto-initialization has been migrated from Firebase Instance ID to Firebase Cloud Messaging. Also its behaviour has slighly changed. Before, a call to deleteInstanceId() would automatically generate a new token if auto-initialization was enabled. Now, the new token is only generated on the next app-start or if getToken() is called explicitly.
private suspend fun loginFCM() = withContext(Dispatchers.Default) {
val fcm = FirebaseMessaging.getInstance()
fcm.isAutoInitEnabled = true
fcm.token.await()
}
private suspend fun logoutFCM() = withContext(Dispatchers.Default) {
val fcm = FirebaseMessaging.getInstance()
fcm.isAutoInitEnabled = false // To prevent a new token to be generated automatically in the next app-start (remove if you don't care)
fcm.deleteToken().await()
}
If you want to logout completely from Firebase you can just delete the whole installation afterwards:
private suspend fun logoutFirebase() = withContext(Dispatchers.Default) {
logoutFCM()
val firebase = FirebaseInstallations.getInstance()
firebase.delete().await()
}
To wrap it all up, use background thread to delete the instanceID, the next time you login keep an eye on the Firestore/Realtime DB (if you save your tokens there), they will refresh
public void Logout() {
new Thread(){
#Override
public void run() {
super.run();
try {
FirebaseInstanceId.getInstance().deleteInstanceId();
FirebaseInstanceId.getInstance().getInstanceId();
} catch (final IOException e) {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(Flags.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
}.start();
FirebaseMessaging.getInstance().setAutoInitEnabled(false);
FirebaseAuth.getInstance().signOut();
SharedPreferences sharedPreferences = getDefaultSharedPreferences(Flags.this);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.apply();
startActivity(new Intent(Flags.this, MainActivity.class));
Flags.this.finish();
}
This code below I used it and it helps me, and I used Kotlin coroutine instead of Thread(Runnable{}).start() because it less cost than creating a new thread object
private fun logoutFromFCM() {
GlobalScope.launch(Dispatchers.IO) {
FirebaseInstallations.getInstance().delete()
FirebaseMessaging.getInstance().deleteToken()
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed", task.exception)
return#OnCompleteListener
}
// Get new FCM registration token
val token = task.result
saveFirebaseToken(token)
Log.w(TAG, "Token Updated - newToken> $token")
})
}
}
For many situations where the notifications requirements are simple, the issue of handling log out can be implemented much more easily. For example, in my case every user is subscribed to only two topics:
Global alerts topic
User specific topic defined as the users email (with replacement of # with - because # is not allowed in topic string)
For such simple scenarios simply unsubscribe from the unwanted topics on log out:
Future<void> signOut() async {
messaging.unsubscribeFromTopic(emailToTopic(_firebaseAuth.currentUser.email));
await _firebaseAuth.signOut();
}
And of course subscribe to topics only on successful log in or sign up:
Future<String> signIn({String email, String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
messaging.subscribeToTopic(emailToTopic(email));
return "Signed in";
} on FirebaseAuthException catch (e) {
return e.message;
}
}
I wanna know that if there is solution to get FCM token while it's null? In other words, I want to call FCM manually to get token while it is null in Android application?
If you want to get the token faster you have to use:
token = FirebaseInstanceId.getInstance().getToken("projectNumber","FCM");
instead of:
token = FirebaseInstanceId.getInstance().getToken();
Use getInstanceId() instead of getToken(),its deprecated.
new Thread(new Runnable() {
#Override
public void run() {
FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(new OnSuccessListener<InstanceIdResult>() {
#Override
public void onSuccess(InstanceIdResult instanceIdResult) {
String deviceToken = instanceIdResult.getToken();
}
});
}
}).start();
If you get null when calling FirebaseInstanceID.getToken(), it means that the token hasn't been generated yet. There is no API to force generating the token now, so you'll have to instead monitor token generation.
To monitor token generation, create a class that derives from com.google.firebase.iid.FirebaseInstanceId and override its onTokenRefresh() method:
#Override
public void onTokenRefresh() {
// Get updated InstanceID token.
String refreshedToken = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Refreshed token: " + refreshedToken);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(refreshedToken);
}
Note that both getting and monitoring the token are covered in the Firebase documentation on accessing the registration token, which is where I got the above code sample from.
How do you refresh the access token using Cognito for Android? The documentation suggest the following (https://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-android-sdk.html):
// Implement authentication handler
AuthenticationHandler handler = new AuthenticationHandler {
#Override
public void onSuccess(CognitoUserSession userSession) {
// Authentication was successful, the "userSession" will have the current valid tokens
// Time to do awesome stuff
}
#Override
public void getAuthenticationDetails(final AuthenticationContinuation continuation, final String userID) {
// User authentication details, userId and password are required to continue.
// Use the "continuation" object to pass the user authentication details
// After the user authentication details are available, wrap them in an AuthenticationDetails class
// Along with userId and password, parameters for user pools for Lambda can be passed here
// The validation parameters "validationParameters" are passed in as a Map<String, String>
AuthenticationDetails authDetails = new AuthenticationDetails(userId, password, validationParameters);
// Now allow the authentication to continue
continuation.setAuthenticationDetails(authDetails);
continuation.continueTask();
}
#Override
public void getMFACode(final MultiFactorAuthenticationContinuation continuation) {
// Multi-factor authentication is required to authenticate
// A code was sent to the user, use the code to continue with the authentication
// Find where the code was sent to
String codeSentHere = continuation.getParameter()[0];
// When the verification code is available, continue to authenticate
continuation.setMfaCode(code);
continuation.continueTask();
}
#Override
public void authenticationChallenge(final ChallengeContinuation continuation) {
// A custom challenge has to be solved to authenticate
// Set the challenge responses
// Call continueTask() method to respond to the challenge and continue with authentication.
}
#Override
public void onFailure(final Exception exception) {
// Authentication failed, probe exception for the cause
}
};
user.getSession(handler);
Here is why this does not work. The user object which I am getting the Session for is no longer authenticated when the token expires. So retrieving the cached user via the below, will return null
CognitoUser user = userPool.getCurrentUser();
Because the above returns null, I try to get the user object by id
CognitoUser user = userPool.getUser(userId);
Which works perfectly, except that user is not authenticated and will fail during the following callback stage because the userID is null
#Override
public void getAuthenticationDetails(final AuthenticationContinuation continuation, final String userID)
Only when I attempt this call before the token expires does this work, and I can receive a new access token. But how to do this after the token has expired? Any help on this would be appreciated. Thanks in advance
When you call getSession(...) - to get tokens - and if the cached tokens have expired, the SDK will automatically refresh tokens (as long as the refresh token has not expired). If the refresh token too has expired, then getAuthenticationDetails(...) is invoked because now the user credentials (username, password, etc) are required to get new set of tokens. It should not matter how you get the user object, i.e. through getCurrentUser() or getUser(...) methods, as long as there are valid cached tokens or if the tokens can be refreshed, you will get valid tokens with getSession(...).
Retry with the latest SDK (ver 2.3.1).