Migra tu app de Parse para iOS a Firebase

Si eres usuario de Parse y buscas un backend alternativo como una solución de servicio, Firebase puede ser la opción que necesitas en tus apps para iOS.

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 iOS+.

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
[PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];

NSDictionary *dimensions = @{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
};
// Send the dimensions to Parse along with the 'search' event
[PFAnalytics trackEvent:@"search" dimensions:dimensions];

Google Analytics

// Obtain the AppMeasurement instance and start collecting data
[FIRApp configure];

// Send the event with your params
[FIRAnalytics logEventWithName:@"search" parameters:@{
  // Define ranges to bucket data points into meaningful segments
  @"priceRange": @"1000-1500",
  // Did the user filter the query?
  @"source": @"craigslist",
  // Do searches happen more often on weekdays or weekends?
  @"dayType": @"weekday"
}];

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

Todos los datos de Firebase Realtime Database se almacenan como objetos JSON y no hay equivalente para PFObject. Simplemente, puedes escribir en los tipos de valores del árbol JSON que corresponden a los tipos JSON disponibles.

El siguiente es un ejemplo de cómo podrías guardar las puntuaciones altas para un juego.

Parse
PFObject *gameScore = [PFObject objectWithClassName:@"GameScore"];
gameScore[@"score"] = @1337;
gameScore[@"playerName"] = @"Sean Plott";
gameScore[@"cheatMode"] = @NO;
[gameScore saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succeeded) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];
NSString *key = [[ref child:@"scores"] childByAutoId].key;
NSDictionary *score = @{@"score": @1337,
                        @"playerName": @"Sean Plott",
                        @"cheatMode": @NO};
[key setValue:score withCompletionBlock:^(NSError *error,  FIRDatabaseReference *ref) {
  if (error) {
    // The object has been saved.
  } else {
    // There was a problem, check error.description
  }
}];
Para obtener más detalles, consulta la guía Lee y escribe datos en las plataformas de Apple.

Relaciones entre datos

Un PFObject puede tener una relación con otro PFObject, 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
PFObject *myAuthor = [PFObject objectWithClassName:@"Author"];
myAuthor[@"name"] = @"Grace Hopper";
myAuthor[@"birthDate"] = @"December 9, 1906";
myAuthor[@"nickname"] = @"Amazing Grace";

// Create the post
PFObject *myPost = [PFObject objectWithClassName:@"Post"];
myPost[@"title"] = @"Announcing COBOL, a New Programming Language";

// Add a relation between the Post and the Author
myPost[@"parent"] = myAuthor;

// This will save both myAuthor and myPost
[myPost saveInBackground];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// Create the author
NSString *myAuthorKey = @"ghopper";
NSDictionary *author = @{@"name": @"Grace Hopper",
                         @"birthDate": @"December 9, 1906",
                         @"nickname": @"Amazing Grace"};
// Save the author
[[ref child:myAuthorKey] setValue:author]

// Create and save the post
NSString *key = [[ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"author": myAuthorKey,
                       @"title": @"Announcing COBOL, a New Programming Language"};
[key 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.

Lee datos

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

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
PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
[query whereKey:@"playerName" equalTo:@"Dan Stemkoski"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
  if (!error) {
    for (PFObject *score in objects) {
      NSString *gameScore = score[@"score"];
      NSLog(@"Retrieved: %@", gameScore);
    }
  } else {
    // Log details of the failure
    NSLog(@"Error: %@ %@", error, [error userInfo]);
  }
}];
Firebase
// Create a reference to the database
FIRDatabaseReference *ref = [[FIRDatabase database] reference];

// This type of listener is not one time, and you need to cancel it to stop
// receiving updates.
[[[[ref child:@"scores"] queryOrderedByChild:@"playerName"] queryEqualToValue:@"Dan Stemkoski"]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
  // This will fire for each matching child node.
  NSDictionary *score = snapshot.value;
  NSString gameScore = score[@"score"];
  NSLog(@"Retrieved: %@", gameScore);
}];
Para conocer más detalles sobre los tipos disponibles de objetos de escucha de eventos y sobre cómo ordenar y filtrar datos, consulta la guía Lee y escribe datos en las plataformas de Apple.

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 PFUser que controla automáticamente la funcionalidad necesaria para la administración de cuentas de usuario. PFUser es una subclase de PFObject, 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 PFObject.

Un FIRUser 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 FIRUser 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
PFUser *user = [PFUser user];
user.username = @"my name";
user.password = @"my pass";
user.email = @"email@example.com";

// other fields can be set just like with PFObject
user[@"phone"] = @"415-392-0202";

[user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (!error) {
    // Hooray! Let them use the app now.
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];
Firebase
[[FIRAuth auth] createUserWithEmail:@"email@example.com"
                           password:@"my pass"
                         completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  if (!error) {
    FIRDatabaseReference *ref = [[FIRDatabase database] reference];
    [[[[ref child:@"users"] child:user.uid] child:@"phone"] setValue:@"415-392-0202"
  } else {
    // Something went wrong
    NSString *errorString = [error userInfo][@"error"];
  }
}];

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 Firebase CLI 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 PFUser, 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

Parse usa tokens de dispositivo de APNS a fin de segmentar las notificaciones para las instalaciones. En cambio, FCM usa tokens de registro de FCM que se asignan a los tokens de dispositivo del APNS. Solo debes agregar el SDK de FCM a tu app para Apple a fin de que este recupere un token de FCM automáticamente.

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.

Por ejemplo, si tu usuario se suscribió al tema "Giants", debes hacer algo como lo siguiente:

PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succedeed) {
    [[FIRMessaging messaging] subscribeToTopic:@"/topics/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 de temas de 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 PFConfig en el cliente. Cada instancia de PFConfig que obtienes es inmutable siempre. Cuando, en el futuro, recuperes una nueva PFConfig desde la red, no modificará ninguna instancia de PFConfig existente, sino que creará una nueva y hará que esté disponible a través de currentConfig.

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

[PFConfig getConfigInBackgroundWithBlock:^(PFConfig *config, NSError *error) {
  if (!error) {
    NSLog(@"Yay! Config was fetched from the server.");
  } else {
    NSLog(@"Failed to fetch. Using Cached Config.");
    config = [PFConfig currentConfig];
  }

  NSString *welcomeMessage = config[@"welcomeMessage"];
  if (!welcomeMessage) {
    NSLog(@"Falling back to default message.");
    welcomeMessage = @"Welcome!";
  }
}];

Firebase

FIRRemoteConfig remoteConfig = [FIRRemoteConfig remoteConfig];
// Set defaults from a plist file
[remoteConfig setDefaultsFromPlistFileName:@"RemoteConfigDefaults"];

[remoteConfig fetchWithCompletionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) {
  if (status == FIRRemoteConfigFetchStatusSuccess) {
    NSLog(@"Yay! Config was fetched from the server.");
    // Once the config is successfully fetched it must be activated before newly fetched
    // values are returned.
    [self.remoteConfig activateFetched];
  } else {
    NSLog(@"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.
NSString welcomeMessage = remoteConfig[@"welcomeMessage"].stringValue;