Migrar o app para iOS do Parse ao Firebase

Se você usa o Parse e procura um back-end alternativo como solução de serviço, o Firebase pode ser a escolha ideal para seu app para iOS.

Veja neste guia como integrar serviços específicos ao app. Para ver instruções básicas de configuração do Firebase, consulte o guia Configuração para iOS.

Google Analytics

O Google Analytics é uma solução gratuita de análise de apps que fornece insights sobre o uso do app e o engajamento do usuário. O Analytics integra-se a recursos do Firebase e oferece a você geração ilimitada de relatórios para até 500 eventos distintos que podem ser definidos usando o SDK do Firebase.

Consulte a documentação do Google Analytics para saber mais.

Sugestão de estratégia de migração

O uso de diferentes provedores de análise é um cenário comum que se aplica facilmente ao Google Analytics. Basta adicioná-lo ao seu app para aproveitar eventos e propriedades de usuário coletados automaticamente pelo Analytics, como primeiro acesso, atualização do app, modelo do dispositivo ou idade.

Para eventos e propriedades de usuário personalizados, é possível empregar uma estratégia dupla usando o Parse Analytics e o Google Analytics para registrar eventos e propriedades. Isso permite que você lance a nova solução gradativamente.

Comparação 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

O Firebase Realtime Database é um banco de dados hospedado na nuvem do NoSQL. Os dados são armazenados como JSON e sincronizados em tempo real para cada cliente conectado.

Consulte a documentação do Firebase Realtime Database para saber mais.

Diferenças ao usar dados do Parse

Objetos

No Parse, é possível armazenar um PFObject ou uma subclasse dele, que contém pares de chave-valor de dados compatíveis com JSON. Os dados não têm esquemas, e isso significa que não é preciso especificar quais chaves existem em cada PFObject.

Todos os dados do Firebase Realtime Database são armazenados como objetos JSON e não existe equivalente para PFObject. Você grava nos valores de árvore JSON dos tipos que correspondem aos tipos JSON disponíveis.

Veja um exemplo de como você pode salvar as pontuações mais altas de um jogo.

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 mais detalhes, consulte o guia Leitura e gravação de dados em plataformas da Apple.

Relacionamentos entre dados

Um PFObject pode ter um relacionamento com outro PFObject: qualquer objeto pode usar outros objetos como valores.

No Firebase Realtime Database, as relações são melhor expressas usando estruturas de dados simples, que dividem os dados em caminhos distintos para que sejam baixados de forma eficiente em chamadas separadas.

Veja um exemplo de como estruturar o relacionamento entre postagens em um app de blog e os respectivos autores.

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]

O resultado é o layout de dados abaixo.

{
  // 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 mais detalhes, consulte o guia Estruturar seu banco de dados.

Como ler dados

No Parse, os dados são lidos usando o código de um objeto específico do Parse ou executando consultas por meio do PFQuery.

No Firebase, os dados são recuperados anexando um listener assíncrono a uma referência do banco de dados. O listener é acionado uma vez para o estado inicial dos dados e novamente quando os dados são alterados, assim não será preciso adicionar código algum para determinar se os dados foram alterados.

Veja um exemplo de como recuperar as pontuações de um determinado jogador com base no exemplo apresentado na seção 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 mais detalhes sobre tipos disponíveis de listener de eventos e sobre como ordenar e filtrar dados, consulte o guia Leitura e gravação de dados em plataformas da Apple.

Sugestão de estratégia de migração

Reconsidere seus dados

O Firebase Realtime Database é otimizado para sincronizar dados em milissegundos em todos os clientes conectados, e a estrutura de dados resultante é diferente dos dados principais do Parse. Isso significa que a primeira etapa da migração é considerar quais alterações seus dados exigem, incluindo:

  • como os objetos do Parse devem mapear dados do Firebase;
  • se você tem relações pai-filho, como dividir os dados entre diferentes caminhos para que os downloads sejam feitos de forma eficiente em chamadas separadas.

Migre os dados

Após decidir como estruturar os dados no Firebase, você precisa planejar como lidar com o período durante o qual o app grava nos dois bancos de dados. Suas opções são:

Sincronização em segundo plano

Nesse cenário, você tem duas versões do app: a versão antiga que usa o Parse e uma nova versão que usa o Firebase. As sincronizações entre os dois bancos de dados são manipuladas pelo Parse Cloud Code (de Parse para Firebase), com o seu código recebendo alterações no Firebase e sincronizando-as com o Parse. Antes de começar a usar a nova versão, você precisa:

  • Converter seus dados do Parse para a nova estrutura do Firebase e gravá-los no Firebase Realtime Database.
  • Escrever funções do Parse Cloud Code que usam a API REST do Firebase para gravar as alterações no Firebase Realtime Database realizadas nos dados do Parse por clientes antigos.
  • gravar e implementar um código que receba alterações no Firebase e sincronize-as no banco de dados do Parse.

Esse cenário garante uma separação limpa dos códigos novos e antigos, simplificando os clientes. Os desafios desse cenário são lidar com grandes conjuntos de dados na exportação inicial e garantir que a sincronização bidirecional não gere recorrência infinita.

Gravação dupla

Nesse cenário você usa o Parse Cloud Code para escrever uma nova versão do app que usa o Firebase e o Parse e sincronizar alterações feitas por antigos clientes nos dados do Parse para o Firebase Realtime Database. Quando a quantidade de pessoas que migraram da versão somente Parse do app for suficiente, você poderá remover o código Parse da versão de gravação dupla.

Esse cenário não requer código do servidor. As desvantagens são que os dados não acessados não serão migrados e o tamanho do seu app será aumentado pelo uso de ambos os SDKs.

Firebase Authentication

O Firebase Authentication pode autenticar usuários que usam senhas e provedores populares de identidades federadas, como Google, Facebook e Twitter. Ele também fornece bibliotecas de IU para economizar o investimento necessário para implementar e manter toda a experiência de autenticação do seu app em todas as plataformas.

Consulte a documentação do Firebase Authentication para saber mais.

Diferenças ao usar o Parse Auth

O Parse fornece uma classe de usuário especializada chamada PFUser, que lida automaticamente com a funcionalidade necessária para o gerenciamento da conta de usuário. PFUser é uma subclasse de PFObject, e isso significa que os dados do usuário estão disponíveis nos dados do Parse e podem ser estendidos com campos adicionais, como qualquer outro PFObject.

Um FIRUser tem um conjunto fixo de propriedades básicas - um ID exclusivo, um endereço de e-mail principal, um nome e um URL de foto - armazenadas em um banco de dados de usuário separado do projeto. Essas propriedades podem ser atualizadas pelo usuário. Não é possível adicionar outras propriedades diretamente ao objeto FIRUser. Em vez disso, armazene as propriedades adicionais no Firebase Realtime Database.

Veja um exemplo de como inscrever um usuário e adicionar um campo adicional de número de telefone.

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"];
  }
}];

Sugestão de estratégia de migração

Migrar contas

Para migrar contas de usuário do Parse para o Firebase, exporte seu banco de dados de usuários para um arquivo JSON ou CSV. Depois, importe esse arquivo para o projeto do Firebase usando o comando auth:import da CLI do Firebase.

Primeiro, exporte seu banco de dados de usuário a partir do console do Parse ou do seu banco de dados auto-hospedado. Por exemplo, um arquivo JSON exportado a partir do console do Parse pode ter a seguinte aparência:

{ // 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",
  ...
}

Em seguida, transforme o arquivo exportado para o formato exigido pela CLI do Firebase. Use o objectId dos seus usuários do Parse como o localId dos seus usuários do Firebase. Além disso, codifique os valores bcryptPassword do Parse em base64 e use-os no campo passwordHash. Exemplo:

{
  "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, importe o arquivo transformado com a CLI do Firebase, especificando bcrypt como o algoritmo de hash:

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

Migrar dados do usuário

Se você estiver armazenando dados adicionais dos seus usuários, será possível migrá-los para o Firebase Realtime Database usando as estratégias descritas na seção Migração de dados. Se você migrar contas usando o fluxo descrito na seção Migração de contas, as contas do Firebase terão os mesmos IDs que as do Parse. Isso permite que você migre e reproduza com facilidade todas as relações usadas pelo ID do usuário.

Firebase Cloud Messaging

O Firebase Cloud Messaging (FCM) é uma solução de mensagens entre plataformas que permite a entrega confiável de mensagens e notificações sem custo. O Editor do Notificações é um serviço sem custos financeiro integrado ao Firebase Cloud Messaging que ativa notificações de usuários direcionadas para desenvolvedores de apps para dispositivos móveis.

Consulte a documentação do Firebase Cloud Messaging para saber mais.

Diferenças ao usar notificações push do Parse

Todo aplicativo Parse instalado em um dispositivo registrado para notificações tem um objeto Installation associado, onde você armazena todos os dados necessários para segmentar as notificações. Installation é uma subclasse de PFUser, e isso significa que é possível adicionar outros dados que quiser às instâncias de Installation.

O Editor do Notificações fornece segmentos de usuários predefinidos com base em informações como app, versão do app e idioma do dispositivo. É possível criar segmentos de usuários mais complexos usando eventos e propriedades do Google Analytics para criar públicos. Para saber mais, consulte o guia de ajuda sobre públicos. Essas informações de segmentação não estão visíveis no Firebase Realtime Database.

Sugestão de estratégia de migração

Migrar tokens de dispositivos

Enquanto o Parse usa tokens de dispositivos de APNs para segmentar instalações destinadas a notificações, o FCM usa tokens de registros de FCM associados aos tokens de dispositivos de APNs. Basta adicionar o SDK do FCM ao seu app da Apple e ele buscará automaticamente um token do FCM.

Migrar canais para tópicos do FCM

Se você estiver usando canais do Parse para enviar notificações, poderá migrar para tópicos do FCM que fornecem o mesmo modelo de editor-assinante. A fim de processar a transição do Parse para o FCM, grave uma nova versão do app que utiliza o SDK do Parse para cancelar a assinatura dos canais do Parse e o SDK do FCM para assinar os tópicos correspondentes do FCM.

Por exemplo, se o usuário estiver inscrito no tópico "Giants", você fará algo como:

PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation removeObject:@"Giants" forKey:@"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
  if (succeeded) {
    [[FIRMessaging messaging] subscribeToTopic:@"/topics/Giants"];
  } else {
    // Something went wrong unsubscribing
  }
}];

Usando essa estratégia, você pode enviar mensagens para o canal do Parse e para o tópico correspondente do FCM, dando suporte a usuários das versões antigas e novas. Quando a quantidade de usuários que migraram da versão somente Parse do app for suficiente, desative essa versão e comece o envio usando somente o FCM.

Consulte os documentos de tópicos do FCM para saber mais.

Firebase Remote Config

A Firebase Remote Config é um serviço em nuvem que permite a alteração do comportamento e da aparência do app sem exigir que os usuários façam download de uma atualização do app. Ao usar a Configuração remota, você cria valores padrão no app que controlam o comportamento e a aparência dele. Em seguida, use o Console do Firebase caso queira modificar os valores padrão no app para todos os usuários do app ou para segmentos da sua base de usuários.

A Firebase Remote Config pode ser muito útil durante as migrações nos casos em que você queira testar diferentes soluções e transferir dinamicamente mais clientes para um provedor diferente. Por exemplo, se você tem uma versão do app que utiliza o Firebase e o Parse para os dados, pode usar uma regra percentual aleatória para determinar quais clientes leem os dados do Firebase e gradativamente aumentar o percentual.

Para saber mais sobre a Firebase Remote Config, consulte a introdução à Remote Config.

Diferenças ao usar a configuração do Parse

Com a configuração do Parse, é possível adicionar pares de chave-valor ao seu app no painel de configuração do Parse e, depois, buscar PFConfig no cliente. Cada instância do PFConfig obtida sempre é imutável. Quando você recupera um novo PFConfig no futuro a partir da rede, as instâncias existentes do PFConfig não são modificadas, mas, em vez disso, uma nova instância é criada e disponibilizada por meio do currentConfig.

Com a Firebase Remote Config, você cria padrões no app para pares de chave-valor que podem ser modificados no console do Firebase e pode usar regras e condições que oferecem variações da experiência do usuário do app para segmentos diferentes da base de usuários. A Firebase Remote Config implementa uma classe singleton que disponibiliza os pares de chave-valor no seu app. Inicialmente, o singleton retorna os valores padrão definidos no app. Busque um novo conjunto de valores no servidor quando for conveniente para o app. Depois que o novo conjunto for buscado, será possível escolher quando ativá-lo para disponibilizar os novos valores para o app.

Sugestão de estratégia de migração

Você pode mudar para a Firebase Remote Config copiando os pares de chave-valor da configuração do Parse para o console do Firebase e implementando uma nova versão do app que usa a Firebase Remote Config.

Se quer fazer uma experiência com a Configuração do Parse e com a Firebase Remote Config, você pode implementar uma nova versão do app que utiliza ambos os SDKs até a quantidade de usuários que migraram da versão somente Parse ser suficiente.

Comparação 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;