'Firebase FCM force onTokenRefresh() to be called
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?
Solution 1:[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.
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:[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:[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:[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:[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");
}
Solution 6:[6]
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:[7]
FirebaseMessaging.getInstance().getToken().addOnSuccessListener(new
OnSuccessListener<String>() {
@Override
public void onSuccess(String newToken) {
....
}
});
Solution 8:[8]
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:[9]
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:[10]
[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:[11]
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:[12]
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:[13]
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. :)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow