Si eres un usuario de Parse que busca un backend alternativo como solución de servicio, Firebase puede ser la opción ideal para tu app de Android.
En esta guía, se describe cómo integrar servicios específicos en tu app. Si quieres 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, puedes escribir en los tipos de valores del árbol JSON que corresponden
a los tipos JSON disponibles.
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);
Relaciones entre datos
Un ParseObject
puede tener una relación con otro ParseObject
, es decir, 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 independientes, 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" } ... } }
Lee 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, debes adjuntar un objeto 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());
}
});
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 controla 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 del usuario están disponibles en los datos de Parse y se pueden extender con
campos adicionales, como 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 la URL de una foto) almacenado en la base de datos de usuarios de un proyecto independiente. El usuario puede actualizar esas
propiedades. No se pueden agregar otras propiedades al objeto FirebaseUser
directamente.
En lugar de ello, puedes almacenar las propiedades adicionales en Firebase Realtime Database.
En el siguiente ejemplo, se 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 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 el
localId
de tus usuarios de Firebase. Además, codifica en Base64 los valores
bcryptPassword
de Parse 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 de migración de cuentas, tus cuentas de Firebase tendrán los mismos ID que tus cuentas de Parse, lo que te permite migrar y reproducir fácilmente cualquier relación que use el ID del 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 desarrolladores 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 segmentar las notificaciones.
Installation
es una subclase de ParseUser
, lo que significa que puedes agregar
cualquier dato adicional 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 quieres evitar que eso suceda, puedes configurar el SDK de Parse para usar el ID de remitente de Parse y tu ID de remitente. De esta manera, no invalidas el token que usa el SDK de Parse, 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 su comportamiento y aspecto. Luego, puedes usar Firebase console a fin de anular los valores predeterminados en la app, ya sea para todos los usuarios 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,
recuperar la ParseConfig
en el cliente. Cada instancia de ParseConfig
que
obtienes es inmutable siempre. Cuando, en el futuro, recuperes una nueva ParseConfig
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 a diferentes segmentos de la base de usuarios. Firebase Remote Config implementa una clase singleton que pone los pares clave-valor a disposición de la app. Inicialmente, el singleton muestra los valores predeterminados que defines en la app. Puedes recuperar un conjunto de valores nuevo desde el servidor en cualquier momento conveniente. Una vez que el conjunto nuevo se haya recuperado con éxito, podrás elegir cuándo activarlo para poner los valores nuevos a disposición de tu 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 que 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");