Ir a la consola

Migra tu app de Parse para Android a Firebase

Si eres un usuario de Parse que busca un back-end alternativo como solución de servicio, Firebase puede ser la opción ideal para tu app de Android.

Esta guía describe cómo integrar servicios específicos con tu app. Si deseas ver las instrucciones básicas para la configuración de Firebase, consulta la guía de configuración para Android.

Google Analytics

Google Analytics es una solución de medición de apps gratuita que proporciona estadísticas sobre el uso de las apps y la participación de los usuarios. Analytics se integra a distintas funciones de Firebase y te proporciona una capacidad ilimitada de generar informes sobre un total de hasta 500 eventos distintos que puedes definir con el SDK de Firebase.

Consulta la documentación de Google Analytics para obtener más información.

Estrategia de migración sugerida

Usar varios proveedores de herramientas de estadísticas es una situación común que se aplica con facilidad a Google Analytics. Solo agrégalo a tu app para aprovechar los eventos y las propiedades del usuario que Analytics recopila automáticamente, como la primera apertura, la actualización de la app, el modelo de dispositivo y la edad, entre otras.

Para las propiedades del usuario y los eventos personalizados, puedes emplear una estrategia de escritura doble, tanto con Parse Analytics como con Google Analytics, para registrar eventos y propiedades, lo que te permite implementar la solución nueva de manera gradual.

Comparación de código

Parse Analytics

// 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 Analytics

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

Firebase Realtime Database

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

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

Diferencias con los datos de Parse

Objetos

En Parse, almacenas un ParseObject o una subclase de este que contiene pares clave-valor de datos compatibles con el formato JSON. Los datos no tienen esquema, por lo que no necesitas especificar qué claves existen en cada ParseObject.

Todos los datos de Firebase Realtime Database se almacenan como objetos JSON y no hay equivalente para ParseObject; simplemente, escribes a los tipos de valores del árbol de JSON que corresponden a los tipos disponibles de JSON. Puedes usar los objetos Java para simplificar la lectura y escritura desde la base de datos.

El siguiente es un ejemplo de cómo podrías guardar los puntajes máximos para un juego.

Parse
@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();
Firebase
// 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, consulta la guía Lectura y escritura de datos en Android.

Relaciones entre datos

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

En Firebase Realtime Database, las relaciones se expresan mejor a través de estructuras compactas de datos que dividen los datos en rutas de acceso diferentes, de modo que se pueden descargar de forma eficiente en llamadas separadas.

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

Parse
// 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();
Firebase
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 siguiente diseño de datos es el resultado.

{
  // 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 ver más detalles, consulta la guía Cómo estructurar tu base de datos.

Lectura de datos

En Parse, los datos se leen mediante el ID de un objeto Parse específico o a través de consultas con ParseQuery.

Para recuperar datos en Firebase, debe adjuntar un agente de escucha asíncrono a la referencia de base de datos. El agente de escucha se activa una vez para el estado inicial de los datos y nuevamente cuando se cambian los datos, de modo que no necesitarás agregar código para determinar si los datos cambiaron.

El siguiente ejemplo muestra cómo podrías recuperar las puntuaciones de un jugador en particular, de acuerdo con el ejemplo que se presentó en la sección "Objetos".

Parse
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());
        }
    }
});
Firebase
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 conocer más detalles sobre los tipos disponibles de agentes de escucha de eventos y sobre cómo ordenar y filtrar datos, consulta la guía Lectura y escritura de datos en Android.

Estrategia de migración sugerida

Reconsidera tus datos

Firebase Realtime Database está optimizado para sincronizar datos entre todos los clientes conectados en milisegundos, y la estructura de datos resultante es diferente de los datos principales de Parse. Esto significa que el primer paso de tu migración es considerar qué cambios necesitan tus datos, incluido lo siguiente:

  • Las relaciones de asignación entre tus objetos de Parse y los datos de Firebase.
  • Si tienes relaciones entre elementos superiores y secundarios, cómo dividir tus datos en rutas de acceso diferentes para que se puedan descargar de forma eficiente en llamadas separadas.

Migra tus datos

Una vez que hayas decidido cómo estructurar tus datos en Firebase, necesitas planificar cómo administrar el período durante el cual tu app deberá escribir en ambas bases de datos. Tus opciones son las siguientes:

Sincronización en segundo plano

En esta situación, tienes dos versiones de la app: la versión antigua que usa Parse y una versión nueva que usa Firebase. Las sincronizaciones entre las dos bases de datos se administran por medio de Parse Cloud Code (de Parse a Firebase), y tu código escucha los cambios en Firebase y sincroniza esos cambios con Parse. Antes de comenzar a usar la versión nueva, debes hacer lo siguiente:

  • Convertir los datos de Parse existentes a la estructura nueva de Firebase y escribirlos en Firebase Realtime Database.
  • Escribir funciones de Parse Cloud Code que usen la API de REST de Firebase para escribir en Firebase Realtime Database los cambios que hagan los clientes antiguos en los datos de Parse.
  • Escribir e implementar código que escuche los cambios en Firebase y los sincronice con la base de datos de Parse.

Esta situación garantiza que exista una separación clara entre el código anterior y el nuevo y evita complicaciones en los clientes. Los desafíos de esta situación son administrar grandes conjuntos de datos en la exportación inicial y garantizar que la sincronización bidireccional no genere una recursividad infinita.

Escritura doble

En esta situación, escribes una versión nueva de la app que use tanto Firebase como Parse, y usas Parse Cloud Code para sincronizar los cambios que hagan los clientes antiguos desde los datos de Parse a Firebase Realtime Database. Cuando suficientes usuarios se migren desde la versión solo para Parse de la app, puedes quitar el código de Parse de la versión de escritura doble.

En esta situación, no es necesario agregar código en el servidor. Las desventajas son que no se migran los datos que nadie consulta y que el tamaño de tu app aumenta, ya que debes usar dos SDK.

Firebase Authentication

Firebase Authentication puede autenticar usuarios con contraseñas y proveedores de identidades federadas populares, como Google, Facebook y Twitter. También proporciona bibliotecas de IU para que no tengas que hacer una inversión significativa en implementar y mantener una experiencia plena de autenticación para tu app en todas las plataformas.

Consulta los documentos de Firebase Authentication para obtener más información.

Diferencias con la autenticación de Parse

Parse proporciona una clase de usuario especializada llamada ParseUser que administra automáticamente la funcionalidad necesaria para la administración de cuentas de usuario. ParseUser es una subclase de ParseObject, lo que significa que los datos de usuario están disponibles en los datos de Parse y se pueden extender con campos adicionales, al igual que cualquier otro ParseObject.

Un FirebaseUser tiene un conjunto fijo de propiedades básicas (un ID único, una dirección de correo electrónico principal, un nombre y una URL de foto) que se almacenan en la base de datos de usuarios de un proyecto independiente. El usuario puede actualizar esas propiedades. No puedes agregar otras propiedades al objeto FirebaseUser directamente; en lugar de ello, puedes almacenar las propiedades adicionales en Firebase Realtime Database.

El siguiente ejemplo muestra cómo podrías registrar un usuario y agregar un campo adicional para el número de teléfono.

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

Migra las cuentas

Para migrar cuentas de usuario de Parse a Firebase, exporta tu base de datos de usuarios a un archivo JSON o CSV y, luego, impórtalo al proyecto de Firebase con el comando auth:import de Firebase CLI.

Primero, exporta tu base de datos de usuarios desde la consola de Parse o tu base de datos local. Por ejemplo, un archivo JSON exportado desde la consola de Parse se podría ser similar al siguiente:

{ // 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, transforma el archivo exportado al formato necesario para Firebase CLI. Usa el objectId de tus usuarios de Parse como localId de tus usuarios de Firebase. Además, codifica los valores bcryptPassword de Parse en Base 64 y úsalos 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
        }
      ]
    }
  ]
}

Por último, importa el archivo transformado con la CLI de Firebase y selecciona bcrypt como el algoritmo de hash.

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

Migra los datos de los usuarios

Si almacenas datos adicionales sobre los usuarios, puedes migrarlos a Firebase Realtime Database con las estrategias que se describen en la sección migración de datos. Si migras cuentas mediante el flujo que se describe en la sección migración de cuentas, tus cuentas de Firebase tendrán los mismos ID que tus cuentas de Parse, lo cual te permite migrar y reproducir fácilmente cualquier relación que use el ID de usuario como clave.

Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) es una solución de mensajería multiplataforma que te permite enviar mensajes y notificaciones de forma segura y gratuita. El compositor de Notifications es un servicio gratuito integrado en Firebase Cloud Messaging que permite que los programadores de apps para dispositivos móviles envíen notificaciones orientadas a los usuarios.

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

Diferencias con las notificaciones push de Parse

Cada aplicación de Parse instalada en un dispositivo registrado para recibir notificaciones tiene un objeto Installation asociado, en el que se almacenan todos los datos necesarios para orientar las notificaciones. Installation es una subclase de ParseUser, lo que significa que puedes agregar los datos adicionales que desees a tus instancias de Installation.

El Compositor de Notifications proporciona segmentos de usuarios predefinidos según datos como la app, la versión de la app y el idioma del dispositivo. Puedes crear segmentos de usuarios más complejos con eventos y propiedades de Google Analytics para crear públicos. Consulta la guía de ayuda sobre públicos para obtener más información. Estos datos de orientación no se pueden ver en Firebase Realtime Database.

Estrategia de migración sugerida

Migra los tokens de dispositivo

Actualmente, el SDK de Parse para Android usa una versión más antigua de los tokens de registro de FCM que no es compatible con las funciones del compositor de Notifications.

Puedes obtener un token nuevo si agregas el SDK de FCM a tu app; sin embargo, esto podría hacer que el token que usa el SDK de Parse para recibir notificaciones deje de ser válido. Si deseas evitar esto, puedes configurar el SDK de Parse para que use el ID de emisor de Pase y tu ID de emisor. De este modo, el token que usa el SDK de Parse no deja de ser válido, pero ten en cuenta que este método alternativo dejará de funcionar cuando Parse cierre su proyecto.

Migra los canales a temas de FCM

Si estás usando canales Parse para enviar notificaciones, puedes migrarlos a temas de FCM, que proporcionan el mismo modelo de editor y suscriptor. Para administrar la transición desde Parse a FCM, puedes escribir una versión nueva de la app que use el SDK de Parse para anular la suscripción desde los canales de Parse y el SDK de FCM para suscribirse a los temas de FCM correspondientes. En esta versión de la app, debes inhabilitar la recepción de notificaciones en el SDK de Parse y quitar lo siguiente del manifiesto de tu app:

<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 se suscribió al tema "Giants", debes hacer algo como lo siguiente:

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, puedes enviar mensajes al canal de Parse y al tema de FCM correspondiente, con lo cual se satisfacen las necesidades de los usuarios de ambas versiones, la antigua y la nueva. Una vez que haya migrado una cantidad suficiente de usuarios de la versión de la app compatible solo con Parse, puedes retirar esa versión y comenzar a enviar solo con FCM.

Consulta los documentos sobre FCM para obtener más información.

Firebase Remote Config

Firebase Remote Config es un servicio en la nube que te permite cambiar el aspecto y el comportamiento de tu app sin pedirles a los usuarios que descarguen una actualización de la app. Cuando usas Remote Config, creas valores predeterminados en la app que controlan el comportamiento y el aspecto de tu app. Luego, puedes usar Firebase console para anular los valores predeterminados para todos los usuarios de la app o para segmentos de tu base de usuarios.

Firebase Remote Config puede ser muy útil durante tus migraciones en casos en los que quieras probar diferentes soluciones y poder cambiar más clientes a un proveedor diferente de forma dinámica. Por ejemplo, si tienes una versión de tu app que usa tanto Firebase como Parse para los datos, puedes usar una regla de percentil aleatorio para determinar qué clientes leen los datos 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, puedes agregar pares clave-valor a tu app en el panel de configuración de Parse y luego puedes obtener la ParseConfig en el cliente. Cada instancia de ParseConfig que obtienes es invariable siempre. Cuando recuperes una nueva ParseConfig en el futuro desde la red, no modificará ninguna instancia de ParseConfig existente, sino que creará una nueva y hará que esté disponible a través de getCurrentConfig().

Con Firebase Remote Config, creas valores predeterminados en la app para los pares clave-valor que pueden anularse desde Firebase console, y puedes usar reglas y condiciones para proporcionar variaciones de la experiencia del usuario de tu app en diferentes segmentos. Firebase Remote Config implementa una clase singleton que pone los pares clave-valor a disposición de tu app. Inicialmente, el singleton devuelve los valores predeterminados que defines en la app. Puedes obtener un conjunto nuevo de valores desde el servidor en cualquier momento conveniente para tu app. Después de obtener el conjunto nuevo de forma correcta, puedes elegir cuándo activarlo para poner los valores nuevos a disposición a la app.

Estrategia de migración sugerida

Para cambiarte a Firebase Remote Config, puedes copiar los pares clave-valor de tu configuración de Parse a Firebase console y luego implementar una versión nueva de la app que use Firebase Remote Config.

Si deseas experimentar con la configuración de Parse y con Firebase Remote Config, puedes implementar una versión nueva de la app que use ambos SDK hasta se migren suficientes usuarios desde la versión solo para Parse.

Comparación de código

Parse

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

Firebase

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