Firebase FCM obliga a que se llame a onTokenRefresh ()

111

Estoy migrando mi aplicación de GCM a FCM.

Cuando un nuevo usuario instala mi aplicación, onTokenRefresh()se llama automáticamente. El problema es que el usuario aún no ha iniciado sesión (sin identificación de usuario).

¿Cómo puedo activar el onTokenRefresh()después de que el usuario haya iniciado sesión?

TareK Khoury
fuente
1
Ya se ha hecho una pregunta muy similar en el siguiente enlace. Compruebe si la respuesta es útil para usted: stackoverflow.com/questions/37517254/…
Diego Giorgini

Respuestas:

172

El onTokenRefresh()método se llamará cada vez que se genere un nuevo token. Tras la instalación de la aplicación, se generará inmediatamente (como ha comprobado que es el caso). También se llamará cuando el token haya cambiado.

Según la FirebaseCloudMessagingguía:

Puede orientar las notificaciones a un solo dispositivo específico. En el inicio inicial de su aplicación, el SDK de FCM genera un token de registro para la instancia de la aplicación cliente.

Captura de pantalla

Enlace de origen: https://firebase.google.com/docs/notifications/android/console-device#access_the_registration_token

Esto significa que el registro del token es por aplicación. Parece que le gustaría utilizar el token después de que un usuario haya iniciado sesión. Lo que sugeriría es que guarde el token en el onTokenRefresh()método en el almacenamiento interno o en las preferencias compartidas. Luego, recupere el token del almacenamiento después de que un usuario inicie sesión y registre el token en su servidor según sea necesario.

Si desea forzar manualmente onTokenRefresh(), puede crear un IntentService y eliminar la instancia del token. Luego, cuando llame a getToken, onTokenRefresh()se volverá a llamar al método.

Código de ejemplo:

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

EDITAR

FirebaseInstanceIdService

FirebaseInstanceIdService de clase pública extiende el servicio

Esta clase está obsoleta. A favor de anular onNewToken en FirebaseMessagingService. Una vez que se ha implementado, este servicio se puede eliminar de forma segura.

onTokenRefresh () está en desuso . Usar onNewToken()enMyFirebaseMessagingService

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);
    }
} 
Príncipe
fuente
27
Incluso si se llama a onTokenRefresh () antes de que el usuario inicie sesión, en lugar de hacer el trabajo de almacenarlo localmente, cuando el usuario inicia sesión, puede recuperar el token usando FirebaseInstanceId.getInstance (). GetToken () y enviarlo al servidor para registro. (a menos que desee almacenarlo localmente para eliminar un token antiguo de su servidor)
geekoraul
10
FireBase es inteligente y llamará al método onTokenRefresh (), solo si no hay un token (se elimina o se llama por primera vez) o si sucede algo más y se ha cambiado el token. Si alguien quiere llamar a onTokenRefresh, podría borrar el token y luego llamar a FirebaseInstanceId.getInstance (). GetToken (). La operación FirebaseInstanceId.getInstance (). DeleteInstanceId () debe estar en AsyncTask o en un nuevo hilo, ¡no podría estar en el MainThread!
Stoycho Andreev
3
¿Por qué no simplemente llamar a FirebaseInstanceId.getToken?
esong
1
bueno, funcionó perfectamente al llamar a IntentService, y supongo que no es necesario guardar el token en las preferencias. Dado que el valor no cambia hasta FirebaseInstanceId.getInstance (). DeleteInstanceId (); se llama. Un poco me salvó el día. :)
Detoxic-Soul
5
¿Por qué guardar el token en preferencias compartidas, si puede llamar a FirebaseInstanceId.getInstance (). GetToken () en cualquier momento para obtener su valor?
Alexander Farber
18

Intente implementar FirebaseInstanceIdServicepara obtener el token de actualización.

Acceda al token de registro:

Puede acceder al valor del token extendiendo FirebaseInstanceIdService . Asegúrese de haber agregado el servicio a su manifiesto , luego llame getTokenen el contexto de onTokenRefreshy registre el valor como se muestra:

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

Código completo:

   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.
    }
}

Vea mi respuesta aquí .

EDICIONES:

No debería iniciar un FirebaseInstanceIdService usted mismo.

Se llamará cuando el sistema determine que los tokens deben actualizarse. La aplicación debe llamar a getToken () y enviar los tokens a todos los servidores de aplicaciones.

Esto no se llamará con mucha frecuencia, es necesario para la rotación de claves y para manejar los cambios de ID de instancia debido a:

  • La aplicación elimina el ID de instancia
  • La aplicación se restaura en un nuevo dispositivo Usuario
  • desinstala / reinstala la aplicación
  • El usuario borra los datos de la aplicación

El sistema acelerará el evento de actualización en todos los dispositivos para evitar sobrecargar los servidores de aplicaciones con actualizaciones de tokens.

Pruebe a continuación :

llamaría a FirebaseInstanceID.getToken () en cualquier lugar de su hilo principal (ya sea un servicio, AsyncTask, etc.), almacenar el token devuelto localmente y enviarlo a su servidor. Luego, cada vez que onTokenRefresh()se llame, llamaría a FirebaseInstanceID.getToken () nuevamente, obtendría un nuevo token y lo enviaría al servidor (probablemente también incluya el token anterior para que su servidor pueda eliminarlo, reemplazándolo por el nuevo) .

pRaNaY
fuente
2
Implementé FirebaseInstanceIdService, el problema es que onTokenRefresh () se llama casi inmediatamente después de que el usuario instala la aplicación. necesito que me llamen después de realizar un inicio de sesión /
registro
1
Así que eliminar FirebaseInstanceId actualizará el token, ¡gracias!
Louis CAD
después de GCM a FCM, FirebaseInstanceId.getInstance (). getToken (); siempre devuelve nulo. ¿Alguna solución?
Govinda Paliwal
@TareKhoury Puede llamar a este método donde sea necesario para obtener el token. FirebaseInstanceId.getInstance (). GetToken ();
sssvrock
¿Se llamará @pRaNaY en caso de actualización de la aplicación cliente onTokenRefresh()?
Piyush Kukadiya
2

Mantengo una bandera en la preferencia compartida que indica si el token gcm se envió al servidor o no. En la pantalla de bienvenida cada vez que llamo a un método sendDevicetokenToServer. Este método verifica si la identificación del usuario no está vacía y si gcm envía el estado y luego envía el token al servidor.

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

}

}

En la clase MyFirebaseInstanceIDService

    @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");
}
Prashanth Debbadwar
fuente
2

Chicos, tiene una solución muy simple.

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

Nota: Si su aplicación usó tokens que fueron eliminados por deleteInstanceID, su aplicación deberá generar tokens de reemplazo.

En lugar de eliminar el ID de la instancia, elimine solo el token:

String authorizedEntity = PROJECT_ID;
String scope = "GCM";
InstanceID.getInstance(context).deleteToken(authorizedEntity,scope);
Manav Patadia
fuente
2
No funcionó para mí. Después de llamar a deleteToken (), getToken () devuelve el mismo token que antes y no se llamó a onTokenRefresh.
Lera
1

Esto está en RxJava2 en el escenario cuando un usuario cierra la sesión de su aplicación y otros usuarios inician sesión (misma aplicación) Para volver a generar y llamar al inicio de sesión (si el dispositivo del usuario no tenía conexión a Internet antes en el momento de inicio de la actividad y necesitamos enviar el token api de inicio de sesión)

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

Iniciar sesión

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

);
Aklesh Singh
fuente
0

Esta respuesta no destruye la identificación de la instancia, sino que puede obtener la actual. También almacena uno actualizado en las preferencias compartidas.

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 (cualquier clase en la que desee establecer / obtener preferencias)

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 (extiende 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);
     ....

}

El servicio de Android 2.0 y superior onCreateno se invoca cuando se inicia automáticamente ( fuente ). En onStartCommandsu lugar, se anula y se utiliza. Pero en FirebaseInstanceIdService real, se declara como final y no se puede anular. Sin embargo, cuando iniciamos el servicio usando startService (), si el servicio ya se está ejecutando, se usa su instancia original (lo cual es bueno). ¡Nuestro onCreate () (definido arriba) también fue invocado !.

Use esto al comienzo de MainActivity o en cualquier punto en el que crea que necesita la identificación de la instancia.

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

Y finalmente,

Utility.getFirebaseInstanceId(getApplicationContext())

Tenga en cuenta que puede mejorar aún más esto intentando mover el código startservice () al método getFirebaseInstanceId.

Varun Garg
fuente
Si restablece / ejecuta la aplicación por primera vez, se tarda un tiempo en actualizar el token. Entonces obtendrá una cadena "predeterminada" durante uno o dos minutos.
Varun Garg
0
    [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();
        }
    }
}
Sayed Azharuddin
fuente
0

FirebaseInstanceIdService

Esta clase está obsoleta. A favor de anular onNewToken en FirebaseMessagingService. Una vez que se ha implementado, este servicio se puede eliminar de forma segura.

La nueva forma de hacer esto sería anular el onNewTokenmétodo deFirebaseMessagingService

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

Además, no olvide agregar el servicio en Manifest.xml

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

Cómo actualizo mi deviceToken

Primero, cuando inicio sesión, envío el primer token de dispositivo bajo la colección de usuarios y el usuario que ha iniciado sesión actualmente.

Después de eso, simplemente anulo onNewToken(token:String)en my FirebaseMessagingService()actualizo ese valor si se genera un nuevo token para ese usuario

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

Cada vez que se abre su aplicación, se buscará un nuevo token, si el usuario aún no ha iniciado sesión, no actualizará el token, si el usuario ya ha iniciado sesión, puede comprobar si hay un newToken

Gastón Saillén
fuente