Firebase FCM force onTokenRefresh() to be called

AndroidFirebaseFirebase Cloud-Messaging

Android Problem Overview


I am migrating my app from GCM to FCM.

When a new user installs my app, the onTokenRefresh() is automatically being called. The problem is that the user is not logged in yet (No user id).

How can I trigger the onTokenRefresh() after the user is logged-in?

Android Solutions


Solution 1 - Android

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 - Android

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 - Android

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 - Android

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 - Android

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");
}

Solution 6 - Android

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

The new way to do this would be to override the onNewToken method from FirebaseMessagingService

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);
	}
} 

Also dont forget to add the service in the Manifest.xml

<service
	android:name=".MyFirebaseMessagingService"
	android:stopWithTask="false">
	<intent-filter>
		<action android:name="com.google.firebase.MESSAGING_EVENT" />
	</intent-filter>
</service>

Solution 7 - Android

FirebaseMessaging.getInstance().getToken().addOnSuccessListener(new 
OnSuccessListener<String>() {
    @Override
    public void onSuccess(String newToken) {
        ....
    }
});

Solution 8 - Android

This is in RxJava2 in scenario when one user logout from your app and other users login (Same App) To regerate and call login (If user's device didn't have internet connection earlier at the time of activity start and we need to send token in login api )

Single.fromCallable(() -> FirebaseInstanceId.getInstance().getToken())
            .flatMap( token -> Retrofit.login(userName,password,token))
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(simple -> {
                if(simple.isSuccess){
                    loginedSuccessfully();
                }
            }, throwable -> Utils.longToast(context, throwable.getLocalizedMessage()));

Login

@FormUrlEncoded
@POST(Site.LOGIN)
Single<ResponseSimple> login(@Field("username") String username,
                         @Field("password") String pass,
                         @Field("token") String token

);

Solution 9 - Android

This answer does not destroy instance id, instead it is able to get current one. It also store refreshed one in Shared preferences.

Strings.xml

<string name="pref_firebase_instance_id_key">pref_firebase_instance_id</string>
<string name="pref_firebase_instance_id_default_key">default</string>

Utility.java (any class where you want to set/get preferences)

public static void setFirebaseInstanceId(Context context, String InstanceId) {
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    SharedPreferences.Editor editor;
    editor = sharedPreferences.edit();
    editor.putString(context.getString(R.string.pref_firebase_instance_id_key),InstanceId);
    editor.apply();
}

public static String getFirebaseInstanceId(Context context) {
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    String key = context.getString(R.string.pref_firebase_instance_id_key);
    String default_value = context.getString(R.string.pref_firebase_instance_id_default_key);
    return sharedPreferences.getString(key, default_value);
}

MyFirebaseInstanceIdService.java (extends FirebaseInstanceIdService)

@Override
public void onCreate()
{
    String CurrentToken = FirebaseInstanceId.getInstance().getToken();

    //Log.d(this.getClass().getSimpleName(),"Inside Instance on onCreate");
    String savedToken = Utility.getFirebaseInstanceId(getApplicationContext());
    String defaultToken = getApplication().getString(R.string.pref_firebase_instance_id_default_key);

    if(CurrentToken != null && !savedToken.equalsIgnoreCase(defaultToken))
    //currentToken is null when app is first installed and token is not available
    //also skip if token is already saved in preferences...
    {
        Utility.setFirebaseInstanceId(getApplicationContext(),CurrentToken);
    }
    super.onCreate();
}

@Override
public void onTokenRefresh() {
     .... prev code
      Utility.setFirebaseInstanceId(getApplicationContext(),refreshedToken);
     ....

}

Android 2.0 and above onCreate of service is not invoked when started automatically (source). Instead onStartCommand is overridden and used. But in actual FirebaseInstanceIdService it is declared as final and can't be overridden. However, when we start service using startService(), if service is already running, its original instance is used (which is good). Our onCreate() (defined above) also got invoked!.

Use this in begining of MainActivity or at whichever point you think you need instance id.

MyFirebaseInstanceIdService myFirebaseInstanceIdService = new MyFirebaseInstanceIdService();
Intent intent= new Intent(getApplicationContext(),myFirebaseInstanceIdService.getClass());
//Log.d(this.getClass().getSimpleName(),"Starting MyFirebaseInstanceIdService");
startService(intent); //invoke onCreate

And Finally,

Utility.getFirebaseInstanceId(getApplicationContext())

Note, you can futher enhance this by trying to move startservice() code to getFirebaseInstanceId method.

Solution 10 - Android

    [Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
class MyFirebaseIIDService: FirebaseInstanceIdService
{
    const string TAG = "MyFirebaseIIDService";
    NotificationHub hub;

    public override void OnTokenRefresh()
    {
        var refreshedToken = FirebaseInstanceId.Instance.Token;
        Log.Debug(TAG, "FCM token: " + refreshedToken);
        SendRegistrationToServer(refreshedToken);
    }

    void SendRegistrationToServer(string token)
    {
        // Register with Notification Hubs
        hub = new NotificationHub(Constants.NotificationHubName,
                                    Constants.ListenConnectionString, this);
        Employee employee = JsonConvert.DeserializeObject<Employee>(Settings.CurrentUser);
        //if user is not logged in 
        if (employee != null)
        {
            var tags = new List<string>() { employee.Email};
            var regID = hub.Register(token, tags.ToArray()).RegistrationId;

            Log.Debug(TAG, $"Successful registration of ID {regID}");
        }
        else
        {
            FirebaseInstanceId.GetInstance(Firebase.FirebaseApp.Instance).DeleteInstanceId();
            hub.Unregister();
        }
    }
}

Solution 11 - Android

How I update my deviceToken

First when I login I send the first device token under the user collection and the current logged in user.

After that, I just override onNewToken(token:String) in my FirebaseMessagingService() and just update that value if a new token is generated for that user

class MyFirebaseMessagingService: FirebaseMessagingService() {
    override fun onMessageReceived(p0: RemoteMessage) {
        super.onMessageReceived(p0)
    }

    override fun onNewToken(token: String) {
    super.onNewToken(token)
    val currentUser= FirebaseAuth.getInstance().currentUser?.uid
    if(currentUser != null){
        FirebaseFirestore.getInstance().collection("user").document(currentUser).update("deviceToken",token)
    }
 }
} 

Each time your app opens it will check for a new token, if the user is not yet signed in it will not update the token, if the user is already logged in you can check for a newToken

Solution 12 - Android

As a general approach to solve these issues: I wasn't able to solve my version of this problem with all the stackoverflow articles. What helped me though was to use the assistant in Android Studio-Tools-Firebase. In my case libraries had been missing from the build cradle files.

Solution 13 - Android

A short answer would be that if what you want to do inside your onTokenRefresh() requires that the user be logged in, then wrap all of it inside an:

if (FirebaseAuth.instance.currentUser != null) {
  // All code that requires a logged in user
}

That way nothing will happen until the user is logged in, at which point the method will fire again and stuff will happen. :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTareK KhouryView Question on Stackoverflow
Solution 1 - AndroidPrinceView Answer on Stackoverflow
Solution 2 - AndroidpRaNaYView Answer on Stackoverflow
Solution 3 - AndroidManav PatadiaView Answer on Stackoverflow
Solution 4 - AndroidPiego Il TempoView Answer on Stackoverflow
Solution 5 - AndroidPrashanth DebbadwarView Answer on Stackoverflow
Solution 6 - AndroidAshwin ValentoView Answer on Stackoverflow
Solution 7 - Androidmehrdad seyrafiView Answer on Stackoverflow
Solution 8 - AndroidAklesh SinghView Answer on Stackoverflow
Solution 9 - AndroidVarun GargView Answer on Stackoverflow
Solution 10 - AndroidSayed AzharuddinView Answer on Stackoverflow
Solution 11 - AndroidGastón SaillénView Answer on Stackoverflow
Solution 12 - AndroidJensView Answer on Stackoverflow
Solution 13 - AndroidKarolina HagegårdView Answer on Stackoverflow