Migra tu aplicación Parse de Android a Firebase

Si es un usuario de Parse y busca una solución alternativa de backend como servicio, Firebase podría ser la opción ideal para su aplicación de Android.

Esta guía describe cómo integrar servicios específicos en su aplicación. Para obtener instrucciones básicas de configuración de Firebase, consulta la guía de configuración de Android .

Google analitico

Google Analytics es una solución gratuita de medición de aplicaciones que proporciona información sobre el uso de las aplicaciones y la participación de los usuarios. Analytics se integra con todas las funciones de Firebase y le proporciona informes ilimitados para hasta 500 eventos distintos que puede definir mediante el SDK de Firebase.

Consulte los documentos de Google Analytics para obtener más información.

Estrategia de migración sugerida

El uso de diferentes proveedores de análisis es un escenario común que se aplica fácilmente a Google Analytics. Simplemente agréguelo a su aplicación para beneficiarse de los eventos y las propiedades del usuario que Analytics recopila automáticamente, como la primera apertura, la actualización de la aplicación, el modelo del dispositivo y la edad.

Para eventos personalizados y propiedades de usuario, puede emplear una estrategia de doble escritura utilizando Parse Analytics y Google Analytics para registrar eventos y propiedades, lo que le permite implementar gradualmente la nueva solución.

Comparación de códigos

Análisis de análisis

// Start collecting data
ParseAnalytics.trackAppOpenedInBackground(getIntent());

Map<String, String> dimensions = new HashMap<String, String>();
// Define ranges to bucket data points into meaningful segments
dimensions.put("priceRange", "1000-1500");
// Did the user filter the query?
dimensions.put("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
dimensions.put("dayType", "weekday");

// Send the dimensions to Parse along with the 'search' event
ParseAnalytics.trackEvent("search", dimensions);

Google analitico

// Obtain the FirebaseAnalytics instance and start collecting data
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);

Bundle params = new Bundle();
// Define ranges to bucket data points into meaningful segments
params.putString("priceRange", "1000-1500");
// Did the user filter the query?
params.putString("source", "craigslist");
// Do searches happen more often on weekdays or weekends?
params.putString("dayType", "weekday");

// Send the event
mFirebaseAnalytics.logEvent("search", params);

Base de datos en tiempo real de Firebase

Firebase Realtime Database es una base de datos NoSQL alojada en la nube. Los datos se almacenan como JSON y se sincronizan en tiempo real con cada cliente conectado.

Consulte los documentos de Firebase Realtime Database para obtener más información.

Diferencias con los datos de análisis

Objetos

En Parse almacena un ParseObject , o una subclase del mismo, que contiene pares clave-valor de datos compatibles con JSON. Los datos no tienen esquema, lo que significa que no es necesario especificar qué claves existen en cada ParseObject .

Todos los datos de Firebase Realtime Database se almacenan como objetos JSON y no existe un equivalente para ParseObject ; simplemente escribe en el árbol JSON los valores de los tipos que corresponden a los tipos JSON disponibles. Puede utilizar objetos Java para simplificar la lectura y escritura desde la base de datos.

El siguiente es un ejemplo de cómo puedes guardar las puntuaciones más altas de un juego.

Analizar gramaticalmente
@ParseClassName("GameScore")
public class GameScore {
        public GameScore() {}
        public GameScore(Long score, String playerName, Boolean cheatMode) {
            setScore(score);
            setPlayerName(playerName);
            setCheatMode(cheatMode);
        }

        public void setScore(Long score) {
            set("score", score);
        }

        public Long getScore() {
            return getLong("score");
        }

        public void setPlayerName(String playerName) {
            set("playerName", playerName);
        }

        public String getPlayerName() {
            return getString("playerName");
        }

        public void setCheatMode(Boolean cheatMode) {
            return set("cheatMode", cheatMode);
        }

        public Boolean getCheatMode() {
            return getBoolean("cheatMode");
        }
}

// Must call Parse.registerSubclass(GameScore.class) in Application.onCreate
GameScore gameScore = new GameScore(1337, "Sean Plott", false);
gameScore.saveInBackground();
base de fuego
// Assuming we defined the GameScore class as:
public class GameScore {
        private Long score;
        private String playerName;
        private Boolean cheatMode;

        public GameScore() {}
        public GameScore(Long score, String playerName, Boolean cheatMode) {
            this.score = score;
            this.playerName = playerName;
            this.cheatMode = cheatMode;
        }

        public Long getScore() {
            return score;
        }

        public String getPlayerName() {
            return playerName;
        }

        public Boolean getCheatMode() {
            return cheatMode;
        }
}

// We would save it to our list of high scores as follows:
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
GameScore score = new GameScore(1337, "Sean Plott", false);
mFirebaseRef.child("scores").push().setValue(score);
Para obtener más detalles, consulte la guía Leer y escribir datos en Android .

Relaciones entre datos

Un ParseObject puede tener una relación con otro ParseObject : cualquier objeto puede utilizar otros objetos como valores.

En Firebase Realtime Database, las relaciones se expresan mejor utilizando estructuras de datos planas que dividen los datos en rutas separadas, de modo que se puedan descargar de manera eficiente en llamadas separadas.

El siguiente es un ejemplo de cómo podría estructurar la relación entre las publicaciones en una aplicación de blogs y sus autores.

Analizar gramaticalmente
// Create the author
ParseObject myAuthor = new ParseObject("Author");
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");

// Create the post
ParseObject myPost = new ParseObject("Post");
myPost.put("title", "Announcing COBOL, a New Programming Language");

// Add a relation between the Post and the Author
myPost.put("parent", myAuthor);

// This will save both myAuthor and myPost
myPost.saveInBackground();
base de fuego
DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
// Create the author
Map<String, String> myAuthor = new HashMap<String, String>();
myAuthor.put("name", "Grace Hopper");
myAuthor.put("birthDate", "December 9, 1906");
myAuthor.put("nickname", "Amazing Grace");

// Save the author
String myAuthorKey = "ghopper";
firebaseRef.child('authors').child(myAuthorKey).setValue(myAuthor);

// Create the post
Map<String, String> post = new HashMap<String, String>();
post.put("author", myAuthorKey);
post.put("title", "Announcing COBOL, a New Programming Language");
firebaseRef.child('posts').push().setValue(post);

El resultado es el siguiente diseño de datos.

{
  // Info about the authors
  "authors": {
    "ghopper": {
      "name": "Grace Hopper",
      "date_of_birth": "December 9, 1906",
      "nickname": "Amazing Grace"
    },
    ...
  },
  // Info about the posts: the "author" fields contains the key for the author
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "ghopper",
      "title": "Announcing COBOL, a New Programming Language"
    }
    ...
  }
}
Para obtener más detalles, consulte la guía Estructurar su base de datos .

Lectura de datos

En Parse, lees datos usando el ID de un objeto Parse específico o ejecutando consultas usando ParseQuery .

En Firebase, puedes recuperar datos adjuntando un detector asincrónico a una referencia de base de datos. El oyente se activa una vez para el estado inicial de los datos y nuevamente cuando los datos cambian, por lo que no necesitará agregar ningún código para determinar si los datos cambiaron.

El siguiente es un ejemplo de cómo puedes recuperar puntuaciones de un jugador en particular, basado en el ejemplo presentado en la sección "Objetos" .

Analizar gramaticalmente
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore");
query.whereEqualTo("playerName", "Dan Stemkoski");
query.findInBackground(new FindCallback<ParseObject>() {
    public void done(List<ParseObject> scoreList, ParseException e) {
        if (e == null) {
            for (ParseObject score: scoreList) {
                Log.d("score", "Retrieved: " + Long.toString(score.getLong("score")));
            }
        } else {
            Log.d("score", "Error: " + e.getMessage());
        }
    }
});
base de fuego
DatabaseReference mFirebaseRef = FirebaseDatabase.getInstance().getReference();
Query mQueryRef = mFirebaseRef.child("scores").orderByChild("playerName").equalTo("Dan Stemkoski");

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
mQueryRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot snapshot, String previousChild) {
        // This will fire for each matching child node.
        GameScore score = snapshot.getValue(GameScore.class);
        Log.d("score", "Retrieved: " + Long.toString(score.getScore());
    }
});
Para obtener más detalles sobre los tipos disponibles de detectores de eventos y sobre cómo ordenar y filtrar datos, consulte la guía Leer y escribir datos en Android .

Estrategia de migración sugerida

Reconsidere sus datos

Firebase Realtime Database está optimizada para sincronizar datos en milisegundos en todos los clientes conectados, y la estructura de datos resultante es diferente de los datos principales de Parse. Esto significa que el primer paso de su migración es considerar qué cambios requieren sus datos, incluidos:

  • Cómo deben asignarse sus objetos Parse a los datos de Firebase
  • Si tiene relaciones entre padres e hijos, cómo dividir sus datos en diferentes rutas para que puedan descargarse de manera eficiente en llamadas separadas.

Migra tus datos

Después de decidir cómo estructurar tus datos en Firebase, debes planificar cómo manejar el período durante el cual tu aplicación necesita escribir en ambas bases de datos. Tus opciones son:

Sincronización en segundo plano

En este escenario, tienes dos versiones de la aplicación: la versión anterior que usa Parse y una versión nueva que usa Firebase. Las sincronizaciones entre las dos bases de datos son manejadas por Parse Cloud Code (Parse to Firebase), y su código escucha los cambios en Firebase y los sincroniza con Parse. Antes de poder comenzar a utilizar la nueva versión, debe:

  • Convierta sus datos de análisis existentes a la nueva estructura de Firebase y escríbalos en Firebase Realtime Database.
  • Escriba funciones de Parse Cloud Code que utilicen la API REST de Firebase para escribir en la base de datos Firebase Realtime los cambios realizados en Parse Data por clientes antiguos.
  • Escriba e implemente código que escuche los cambios en Firebase y los sincronice con la base de datos de Parse.

Este escenario garantiza una separación clara del código antiguo y nuevo y mantiene a los clientes simples. Los desafíos de este escenario son manejar grandes conjuntos de datos en la exportación inicial y garantizar que la sincronización bidireccional no genere una recursividad infinita.

Doble escritura

En este escenario, escribe una nueva versión de la aplicación que usa Firebase y Parse, usando Parse Cloud Code para sincronizar los cambios realizados por clientes antiguos desde Parse Data con Firebase Realtime Database. Cuando suficientes personas hayan migrado desde la versión de la aplicación solo de Parse, puede eliminar el código de Parse de la versión de doble escritura.

Este escenario no requiere ningún código del lado del servidor. Sus desventajas son que los datos a los que no se accede no se migran y que el tamaño de su aplicación aumenta mediante el uso de ambos SDK.

Autenticación de base de fuego

Firebase Authentication puede autenticar a los usuarios mediante contraseñas y proveedores de identidades federados populares como Google, Facebook y Twitter. También proporciona bibliotecas de interfaz de usuario para ahorrarle la importante inversión necesaria para implementar y mantener una experiencia de autenticación completa para su aplicación en todas las plataformas.

Consulte los documentos de autenticación de Firebase para obtener más información.

Diferencias con la autenticación de análisis

Parse proporciona una clase de usuario especializada llamada ParseUser que maneja automáticamente la funcionalidad requerida para la administración de cuentas de usuario. ParseUser es una subclase de ParseObject , lo que significa que los datos del usuario están disponibles en Parse Data y se pueden ampliar con campos adicionales como cualquier otro ParseObject .

Un FirebaseUser tiene un conjunto fijo de propiedades básicas (una identificación única, una dirección de correo electrónico principal, un nombre y una URL de foto) almacenadas en una base de datos de usuarios de un proyecto independiente; esas propiedades pueden ser actualizadas por el usuario. No puedes agregar otras propiedades al objeto FirebaseUser directamente; en su lugar, puede almacenar las propiedades adicionales en su Firebase Realtime Database.

El siguiente es un ejemplo de cómo puede registrar un usuario y agregar un campo de número de teléfono adicional.

Analizar gramaticalmente
ParseUser user = new ParseUser();
user.setUsername("my name");
user.setPassword("my pass");
user.setEmail("email@example.com");

// other fields can be set just like with ParseObject
user.put("phone", "650-253-0000");

user.signUpInBackground(new SignUpCallback() {
    public void done(ParseException e) {
        if (e == null) {
            // Hooray! Let them use the app now.
        } else {
            // Sign up didn't succeed. Look at the ParseException
            // to figure out what went wrong
        }
    }
});
base de fuego
FirebaseAuth mAuth = FirebaseAuth.getInstance();

mAuth.createUserWithEmailAndPassword("email@example.com", "my pass")
    .continueWithTask(new Continuation<AuthResult, Task<Void>> {
        @Override
        public Task<Void> then(Task<AuthResult> task) {
            if (task.isSuccessful()) {
                FirebaseUser user = task.getResult().getUser();
                DatabaseReference firebaseRef = FirebaseDatabase.getInstance().getReference();
                return firebaseRef.child("users").child(user.getUid()).child("phone").setValue("650-253-0000");
            } else {
                // User creation didn't succeed. Look at the task exception
                // to figure out what went wrong
                Log.w(TAG, "signInWithEmail", task.getException());
            }
        }
    });

Estrategia de migración sugerida

Migrar cuentas

Para migrar cuentas de usuario de Parse a Firebase, exporte su base de datos de usuario a un archivo JSON o CSV, luego importe el archivo a su proyecto de Firebase usando el comando auth:import de Firebase CLI.

Primero, exporte su base de datos de usuarios desde la consola Parse o su base de datos autohospedada. Por ejemplo, un archivo JSON exportado desde la consola de Parse podría tener el siguiente aspecto:

{ // Username/password user
  "bcryptPassword": "$2a$10$OBp2hxB7TaYZgKyTiY48luawlTuYAU6BqzxJfpHoJMdZmjaF4HFh6",
  "email": "user@example.com",
  "username": "testuser",
  "objectId": "abcde1234",
  ...
},
{ // Facebook user
  "authData": {
    "facebook": {
      "access_token": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
      "expiration_date": "2017-01-02T03:04:05.006Z",
      "id": "1000000000"
    }
  },
  "username": "wXyZ987654321StUv",
  "objectId": "fghij5678",
  ...
}

Luego, transforme el archivo exportado al formato requerido por Firebase CLI. Utilice el objectId de sus usuarios de Parse como el localId de sus usuarios de Firebase. Además, base64 codifica los valores bcryptPassword de Parse y los utiliza en el campo passwordHash . Por ejemplo:

{
  "users": [
    {
      "localId": "abcde1234",  // Parse objectId
      "email": "user@example.com",
      "displayName": "testuser",
      "passwordHash": "JDJhJDEwJE9CcDJoeEI3VGFZWmdLeVRpWTQ4bHVhd2xUdVlBVTZCcXp4SmZwSG9KTWRabWphRjRIRmg2",
    },
    {
      "localId": "fghij5678",  // Parse objectId
      "displayName": "wXyZ987654321StUv",
      "providerUserInfo": [
        {
          "providerId": "facebook.com",
          "rawId": "1000000000",  // Facebook ID
        }
      ]
    }
  ]
}

Finalmente, importe el archivo transformado con Firebase CLI, especificando bcrypt como algoritmo hash:

firebase auth:import account_file.json --hash-algo=BCRYPT

Migrar datos de usuario

Si almacena datos adicionales para sus usuarios, puede migrarlos a Firebase Realtime Database usando las estrategias descritas en la sección de migración de datos . Si migra cuentas utilizando el flujo descrito en la sección de migración de cuentas , sus cuentas de Firebase tienen los mismos identificadores que sus cuentas de Parse, lo que le permite migrar y reproducir fácilmente cualquier relación ingresada por el identificador de usuario.

Mensajería en la nube de Firebase

Firebase Cloud Messaging (FCM) es una solución de mensajería multiplataforma que le permite enviar mensajes y notificaciones de manera confiable y sin costo. El compositor de notificaciones es un servicio sin costo integrado en Firebase Cloud Messaging que permite notificaciones de usuario específicas para desarrolladores de aplicaciones móviles.

Consulte los documentos de Firebase Cloud Messaging para obtener más información.

Diferencias con las notificaciones push de Parse

Cada aplicación Parse instalada en un dispositivo registrado para recibir notificaciones tiene un objeto Installation asociado, donde se almacenan todos los datos necesarios para recibir notificaciones. Installation es una subclase de ParseUser , lo que significa que puede agregar cualquier dato adicional que desee a sus instancias Installation .

El compositor de notificaciones proporciona segmentos de usuarios predefinidos basados ​​en información como la aplicación, la versión de la aplicación y el idioma del dispositivo. Puede crear segmentos de usuarios más complejos utilizando eventos y propiedades de Google Analytics para crear audiencias. Consulte la guía de ayuda para audiencias para obtener más información. Esta información de orientación no es visible en Firebase Realtime Database.

Estrategia de migración sugerida

Migrar tokens de dispositivo

Al momento de escribir este artículo, el SDK de Parse para Android utiliza una versión anterior de los tokens de registro de FCM, que no es compatible con las funciones ofrecidas por el compositor de notificaciones.

Puede obtener un nuevo token agregando el SDK de FCM a su aplicación; sin embargo, esto podría invalidar el token utilizado por Parse SDK para recibir notificaciones. Si desea evitar eso, puede configurar el SDK de Parse para usar tanto el ID de remitente de Parse como su ID de remitente. De esta manera no invalida el token utilizado por Parse SDK, pero tenga en cuenta que esta solución dejará de funcionar cuando Parse cierre su proyecto.

Migración de canales a temas de FCM

Si utiliza canales de Parse para enviar notificaciones, puede migrar a temas de FCM, que proporcionan el mismo modelo de editor-suscriptor. Para manejar la transición de Parse a FCM, puede escribir una nueva versión de la aplicación que utilice el SDK de Parse para cancelar la suscripción a los canales de Parse y el SDK de FCM para suscribirse a los temas de FCM correspondientes. En esta versión de la aplicación, debes desactivar la recepción de notificaciones en el SDK de Parse, eliminando lo siguiente del manifiesto de tu aplicación:

<service android:name="com.parse.PushService" />
<receiver android:name="com.parse.ParsePushBroadcastReceiver"
  android:exported="false">
<intent-filter>
<action android:name="com.parse.push.intent.RECEIVE" />
<action android:name="com.parse.push.intent.DELETE" />
<action android:name="com.parse.push.intent.OPEN" />
</intent-filter>
</receiver>
<receiver android:name="com.parse.GcmBroadcastReceiver"
  android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />

<!--
IMPORTANT: Change "com.parse.starter" to match your app's package name.
-->
<category android:name="com.parse.starter" />
</intent-filter>
</receiver>

<!--
IMPORTANT: Change "YOUR_SENDER_ID" to your GCM Sender Id.
-->
<meta-data android:name="com.parse.push.gcm_sender_id"
  android:value="id:YOUR_SENDER_ID" />;

Por ejemplo, si tu usuario está suscrito al tema "Gigantes", harías algo como:

ParsePush.unsubscribeInBackground("Giants", new SaveCallback() {
    @Override
    public void done(ParseException e) {
        if (e == null) {
            FirebaseMessaging.getInstance().subscribeToTopic("Giants");
        } else {
            // Something went wrong unsubscribing
        }
    }
});

Con esta estrategia, puede enviar mensajes tanto al canal Parse como al tema FCM correspondiente, lo que admite usuarios de versiones antiguas y nuevas. Cuando suficientes usuarios hayan migrado desde la versión de solo Parse de la aplicación, puede desactivar esa versión y comenzar a enviar usando FCM únicamente.

Consulte los documentos de temas de FCM para obtener más información.

Configuración remota de Firebase

Firebase Remote Config es un servicio en la nube que te permite cambiar el comportamiento y la apariencia de tu aplicación sin necesidad de que los usuarios descarguen una actualización de la aplicación. Cuando usas Remote Config, creas valores predeterminados en la aplicación que controlan el comportamiento y la apariencia de tu aplicación. Luego, podrás usar Firebase console para anular los valores predeterminados en la aplicación para todos los usuarios de la aplicación o para segmentos de tu base de usuarios.

Firebase Remote Config puede ser muy útil durante sus migraciones en los casos en que desee probar diferentes soluciones y poder cambiar dinámicamente más clientes a un proveedor diferente. Por ejemplo, si tiene una versión de su aplicación que usa Firebase y Parse para los datos, puede usar una regla de percentil aleatoria para determinar qué clientes leen desde Firebase y aumentar gradualmente el porcentaje.

Para obtener más información sobre Firebase Remote Config, consulta la introducción a Remote Config .

Diferencias con la configuración de Parse

Con la configuración de Parse, puede agregar pares clave/valor a su aplicación en el Panel de configuración de Parse y luego recuperar ParseConfig en el cliente. Cada instancia ParseConfig que obtienes es siempre inmutable. Cuando recupere un nuevo ParseConfig en el futuro de la red, no modificará ninguna instancia ParseConfig existente, sino que creará una nueva y la pondrá a disposición a través de getCurrentConfig() .

Con Firebase Remote Config, creas valores predeterminados en la aplicación para pares clave/valor que puedes anular desde Firebase console, y puedes usar reglas y condiciones para proporcionar variaciones en la experiencia de usuario de tu aplicación a diferentes segmentos de tu base de usuarios. Firebase Remote Config implementa una clase singleton que hace que los pares clave/valor estén disponibles para tu aplicación. Inicialmente, el singleton devuelve los valores predeterminados que usted define en la aplicación. Puede recuperar un nuevo conjunto de valores del servidor en cualquier momento conveniente para su aplicación; Una vez que el nuevo conjunto se haya obtenido correctamente, puede elegir cuándo activarlo para que los nuevos valores estén disponibles para la aplicación.

Estrategia de migración sugerida

Puede pasar a Firebase Remote Config copiando los pares clave/valor de su configuración de Parse en Firebase console y luego implementando una nueva versión de la aplicación que usa Firebase Remote Config.

Si desea experimentar con Parse Config y Firebase Remote Config, puede implementar una nueva versión de la aplicación que use ambos SDK hasta que suficientes usuarios hayan migrado desde la versión exclusiva de Parse.

Comparación de códigos

Analizar gramaticalmente

ParseConfig.getInBackground(new ConfigCallback() {
    @Override
    public void done(ParseConfig config, ParseException e) {
        if (e == null) {
            Log.d("TAG", "Yay! Config was fetched from the server.");
        } else {
            Log.e("TAG", "Failed to fetch. Using Cached Config.");
            config = ParseConfig.getCurrentConfig();
        }

        // Get the message from config or fallback to default value
        String welcomeMessage = config.getString("welcomeMessage", "Welcome!");
    }
});

base de fuego

mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
// Set defaults from an XML resource file stored in res/xml
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);

mFirebaseRemoteConfig.fetch()
    .addOnSuccessListener(new OnSuccessListener<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            Log.d("TAG", "Yay! Config was fetched from the server.");
            // Once the config is successfully fetched it must be activated before newly fetched
            // values are returned.
            mFirebaseRemoteConfig.activateFetched();
        }
    })
    .addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            Log.e("TAG", "Failed to fetch. Using last fetched or default.");
        }
    })

// ...

// When this is called, the value of the latest fetched and activated config is returned;
// if there's none, the default value is returned.
String welcomeMessage = mFirebaseRemoteConfig.getString("welcomeMessage");