Firebase FCM force onTokenRefresh() to be called

Solution 1:

The onTokenRefresh() method is going to be called whenever a new token is generated. Upon app install, it will be generated immediately (as you have found to be the case). It will also be called when the token has changed.

According to the FirebaseCloudMessaging guide:

You can target notifications to a single, specific device. On initial startup of your app, the FCM SDK generates a registration token for the client app instance.

Screenshot

Source Link: https://firebase.google.com/docs/notifications/android/console-device#access_the_registration_token

This means that the token registration is per app. It sounds like you would like to utilize the token after a user is logged in. What I would suggest is that you save the token in the onTokenRefresh() method to internal storage or shared preferences. Then, retrieve the token from storage after a user logs in and register the token with your server as needed.

If you would like to manually force the onTokenRefresh(), you can create an IntentService and delete the token instance. Then, when you call getToken, the onTokenRefresh() method will be called again.

Example Code:

public class DeleteTokenService extends IntentService
{
    public static final String TAG = DeleteTokenService.class.getSimpleName();

    public DeleteTokenService()
    {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent)
    {
        try
        {
            // Check for current token
            String originalToken = getTokenFromPrefs();
            Log.d(TAG, "Token before deletion: " + originalToken);

            // Resets Instance ID and revokes all tokens.
            FirebaseInstanceId.getInstance().deleteInstanceId();

            // Clear current saved token
            saveTokenToPrefs("");

            // Check for success of empty token
            String tokenCheck = getTokenFromPrefs();
            Log.d(TAG, "Token deleted. Proof: " + tokenCheck);

            // Now manually call onTokenRefresh()
            Log.d(TAG, "Getting new token");
            FirebaseInstanceId.getInstance().getToken();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    private void saveTokenToPrefs(String _token)
    {
        // Access Shared Preferences
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        SharedPreferences.Editor editor = preferences.edit();

        // Save to SharedPreferences
        editor.putString("registration_id", _token);
        editor.apply();
    }

    private String getTokenFromPrefs()
    {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        return preferences.getString("registration_id", null);
    }
}

EDIT

FirebaseInstanceIdService

public class FirebaseInstanceIdService extends Service

This class is deprecated. In favour of overriding onNewToken in FirebaseMessagingService. Once that has been implemented, this service can be safely removed.

onTokenRefresh() is deprecated. Use onNewToken() in MyFirebaseMessagingService

public class MyFirebaseMessagingService extends FirebaseMessagingService {

@Override
public void onNewToken(String s) {
    super.onNewToken(s);
    Log.e("NEW_TOKEN",s);
    }

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);
    }
} 

Solution 2:

Try to implement FirebaseInstanceIdService to get refresh token.

Access the registration token:

You can access the token's value by extending FirebaseInstanceIdService. Make sure you have added the service to your manifest, then call getToken in the context of onTokenRefresh, and log the value as shown:

     @Override
public void onTokenRefresh() {
    // Get updated InstanceID token.
    String refreshedToken = FirebaseInstanceId.getInstance().getToken();
    Log.d(TAG, "Refreshed token: " + refreshedToken);

    // TODO: Implement this method to send any registration to your app's servers.
    sendRegistrationToServer(refreshedToken);
}

Full Code:

   import android.util.Log;

import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;


public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {

    private static final String TAG = "MyFirebaseIIDService";

    /**
     * Called if InstanceID token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the InstanceID token
     * is initially generated so this is where you would retrieve the token.
     */
    // [START refresh_token]
    @Override
    public void onTokenRefresh() {
        // Get updated InstanceID token.
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        Log.d(TAG, "Refreshed token: " + refreshedToken);

        // TODO: Implement this method to send any registration to your app's servers.
        sendRegistrationToServer(refreshedToken);
    }
    // [END refresh_token]

    /**
     * Persist token to third-party servers.
     *
     * Modify this method to associate the user's FCM InstanceID token with any server-side account
     * maintained by your application.
     *
     * @param token The new token.
     */
    private void sendRegistrationToServer(String token) {
        // Add custom implementation, as needed.
    }
}

See my answer here.

EDITS:

You shouldn't be starting a FirebaseInstanceIdService yourself.

It will Called when the system determines that the tokens need to be refreshed. The application should call getToken() and send the tokens to all application servers.

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

The system will throttle the refresh event across all devices to avoid overloading application servers with token updates.

Try below way:

you'd call FirebaseInstanceID.getToken() anywhere off your main thread (whether it is a service, AsyncTask, etc), store the returned token locally and send it to your server. Then whenever onTokenRefresh() is called, you'd call FirebaseInstanceID.getToken() again, get a new token, and send that up to the server (probably including the old token as well so your server can remove it, replacing it with the new one).

Solution 3:

Guys it has very simple solution

https://developers.google.com/instance-id/guides/android-implementation#generate_a_token

Note: If your app used tokens that were deleted by deleteInstanceID, your app will need to generate replacement tokens.

In stead of deleting instance Id, delete only token:

String authorizedEntity = PROJECT_ID;
String scope = "GCM";
InstanceID.getInstance(context).deleteToken(authorizedEntity,scope);

Solution 4:

For those who are looking for a way to force the refresh of your token and get this way onNewToken getting called because a new token was generated, you just need to call this whenever you need to do it:

FirebaseMessaging.getInstance().deleteToken().addOnSuccessListener {
  FirebaseMessaging.getInstance().token
}

I wrote it as a static function in MyFirebaseMessagingService like this for semplicity:

class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        if (remoteMessage.data.isNotEmpty()) {
            Log.d(TAG, "Message data payload: ${remoteMessage.data}")
        }

        // do your stuff.
    }

    override fun onNewToken(token: String) {
        Log.d(TAG, "FCM token changed: $token")
        
        // send it to your backend.
    }

    companion object {
        private const val TAG = "MyFirebaseMessagingService"

        fun refreshFcmToken() {
            FirebaseMessaging.getInstance().deleteToken().addOnSuccessListener {
                FirebaseMessaging.getInstance().token
            }
        }
    }
}

Calling just deleteToken() is not enough since the new token will be generated only when it will be requested; of course it is requested everytime you open your app, so if you just call deleteToken() the new token will be generated the next time the user will open the app, but this can lead to problems if you need to send notifications right during or after his first use of the app.

And calling token() right after deleteToken() causes concurrency problems since they're both asynch operations and token() will end the execution always before deleteToken() (because it sees that the token already exist, since it is not deleted yet, and does not even try to generate a new one because of that, while deleteToken() is requesting to Firebase servers to delete the current token).

This is why you need to call token() after the deleteToken() thread has been successfully completed.

Solution 5:

I am maintaining one flag in shared pref which indicates whether gcm token sent to server or not. In Splash screen every time I am calling one method sendDevicetokenToServer. This method checks if user id is not empty and gcm send status then send token to server.

public static void  sendRegistrationToServer(final Context context) {

if(Common.getBooleanPerf(context,Constants.isTokenSentToServer,false) ||
        Common.getStringPref(context,Constants.userId,"").isEmpty()){

    return;
}

String token =  FirebaseInstanceId.getInstance().getToken();
String userId = Common.getUserId(context);
if(!userId.isEmpty()) {
    HashMap<String, Object> reqJson = new HashMap<>();
    reqJson.put("deviceToken", token);
    ApiInterface apiService =
            ApiClient.getClient().create(ApiInterface.class);

    Call<JsonElement> call = apiService.updateDeviceToken(reqJson,Common.getUserId(context),Common.getAccessToken(context));
    call.enqueue(new Callback<JsonElement>() {
        @Override
        public void onResponse(Call<JsonElement> call, Response<JsonElement> serverResponse) {

            try {
                JsonElement jsonElement = serverResponse.body();
                JSONObject response = new JSONObject(jsonElement.toString());
                if(context == null ){
                    return;
                }
                if(response.getString(Constants.statusCode).equalsIgnoreCase(Constants.responseStatusSuccess)) {

                    Common.saveBooleanPref(context,Constants.isTokenSentToServer,true);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onFailure(Call<JsonElement> call, Throwable throwable) {

            Log.d("", "RetroFit2.0 :getAppVersion: " + "eroorrrrrrrrrrrr");
            Log.e("eroooooooorr", throwable.toString());
        }
    });

}

}

In MyFirebaseInstanceIDService class

    @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.
    Common.saveBooleanPref(this,Constants.isTokenSentToServer,false);
    Common.sendRegistrationToServer(this);
    FirebaseMessaging.getInstance().subscribeToTopic("bloodRequest");
}