Se você é um usuário do Parse que procura um back-end alternativo como solução de serviço, o Firebase pode ser a escolha ideal para seu app Android.
Este guia descreve como integrar serviços específicos ao app. Para ver instruções básicas de configuração do Firebase, consulte o guia Configuração do Android.
Google Analytics
O Google Analytics é uma solução sem custo financeiro de análise de apps que fornece insights sobre o uso de apps e o envolvimento 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 os documentos 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ê implante a nova solução gradativamente.
Comparação 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
O Firebase Realtime Database é um banco de dados NoSQL hospedado na nuvem. Os dados são armazenados como JSON e sincronizados em tempo real para cada cliente conectado.
Veja os documentos do Firebase Realtime Database para saber mais.
Diferenças ao usar dados do Parse
Objetos
No Parse, é possível armazenar um ParseObject
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 ParseObject
.
Todos os dados do Firebase Realtime Database são armazenados como objetos JSON e não existe equivalente para
ParseObject
. Você grava nos valores de árvore JSON dos tipos que correspondem
aos tipos JSON disponíveis.
Você pode usar objetos Java para simplificar a leitura e a gravação do banco de dados.
Veja um exemplo de como você pode salvar as maiores pontuações de um jogo.
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 mais detalhes, consulte o guia
Leitura e gravação de dados no Android.
Relacionamentos entre dados
Um ParseObject
pode ter um relacionamento com outro ParseObject
: 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
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);
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 ParseQuery
.
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
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 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 no Android.
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 REST API do Firebase para gravar 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.
Veja os documentos do Firebase Authentication para saber mais.
Diferenças ao usar o Parse Auth
O Parse fornece uma classe de usuário especializada chamada ParseUser
, que lida automaticamente
com a funcionalidade necessária para o gerenciamento da conta de usuário. ParseUser
é uma subclasse de
ParseObject
, 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 ParseObject
.
Um FirebaseUser
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 FirebaseUser
,
mas você tem a opção de armazenar essas outras propriedades no Firebase Realtime Database.
Veja um exemplo de como inscrever um usuário e adicionar um campo adicional de número de telefone.
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());
}
}
});
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 códigos que as do Parse. Isso permite que você migre e reproduza com facilidade todas as relações usadas pelo código 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 custos. O Editor do Notificações é um serviço sem custos integrado ao Firebase Cloud Messaging que ativa notificações de usuários direcionadas para desenvolvedores de aplicativos para dispositivos móveis.
Veja os documentos 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 ParseUser
, 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 por meio de 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
Migração de tokens de dispositivos
No momento da gravação, o SDK para Parse do Firebase usa uma versão mais antiga dos tokens de registro do FCM, incompatível com os recursos oferecidos pelo Editor do Notificações.
Você pode receber um novo token adicionando o SDK do FCM ao app. Porém, isso pode invalidar o token usado pelo SDK para Parse para receber notificações. Se você quiser evitar isso, configure o SDK do Parse para usar o código de remetente do Parse e seu código de remetente. Desta forma, o token usado pelo Parse SDK não é invalidado, mas esteja ciente de que essa solução alternativa deixará de funcionar quando o Parse encerrar o projeto.
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 para Parse para cancelar a assinatura dos canais Parse e o SDK do FCM para assinar os tópicos correspondentes do FCM. Nessa versão do app, você deve desativar o recebimento de notificações no SDK para Parse, removendo do manifesto do app o seguinte:
<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 exemplo, se o usuário estiver inscrito no tópico "Giants", você fará algo como:
ParsePush.unsubscribeInBackground("Giants", new SaveCallback() {
@Override
public void done(ParseException e) {
if (e == null) {
FirebaseMessaging.getInstance().subscribeToTopic("Giants");
} else {
// Something went wrong unsubscribing
}
}
});
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 a enviar usando somente o FCM.
Veja os documentos sobre tópicos do FCM para saber mais.
Configuração remota do Firebase
O Configuração remota do Firebase é 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 aplicativo. 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.
O Configuração remota do Firebase 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 o Configuração remota do Firebase, veja Introdução ao Configuração remota.
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 ParseConfig
no cliente. Cada instância do ParseConfig
obtida
sempre é imutável. Quando você recupera um novo ParseConfig
no futuro a partir da
rede, as instâncias existentes do ParseConfig
não são modificadas, mas, em vez disso,
uma nova instância é criada e disponibilizada por meio do getCurrentConfig()
.
Com o Configuração remota do Firebase, você cria padrões no aplicativo 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 Configuração remota do Firebase 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 Configuração remota do Firebase 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 utiliza a Configuração remota do Firebase.
Se você quer fazer uma experiência com a configuração do Parse e com o Configuração remota do Firebase, 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
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");