Migre seu aplicativo Parse Android para o Firebase

Se você é um usuário do Parse e procura uma solução alternativa de backend como serviço, o Firebase pode ser a escolha ideal para seu aplicativo Android.

Este guia descreve como integrar serviços específicos ao seu aplicativo. Para obter instruções básicas de configuração do Firebase, consulte o guia de configuração do Android .

Google Analytics

O Google Analytics é uma solução gratuita de medição de aplicativos que fornece informações sobre o uso de aplicativos e o envolvimento do usuário. O Analytics se integra aos recursos do Firebase e fornece relatórios ilimitados para até 500 eventos distintos que você pode definir usando o SDK do Firebase.

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

Estratégia de migração sugerida

Usar diferentes provedores de análise é um cenário comum que se aplica facilmente ao Google Analytics. Basta adicioná-lo ao seu aplicativo para se beneficiar dos eventos e propriedades do usuário que o Analytics coleta automaticamente, como primeiro acesso, atualização do aplicativo, modelo do dispositivo, idade.

Para eventos personalizados e propriedades do usuário, você pode empregar uma estratégia de gravação dupla usando o Parse Analytics e o Google Analytics para registrar eventos e propriedades, o que permite implementar gradualmente a nova solução.

Comparação de códigos

Analisar análises

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

Banco de dados em tempo real do Firebase

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.

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

Diferenças com análise de dados

Objetos

No Parse você armazena um ParseObject , ou uma subclasse dele, que contém pares de valores-chave de dados compatíveis com JSON. Os dados não têm esquema, o que significa que você não precisa especificar quais chaves existem em cada ParseObject .

Todos os dados do Firebase Realtime Database são armazenados como objetos JSON e não há equivalente para ParseObject ; você simplesmente escreve na árvore JSON valores de tipos que correspondem aos tipos JSON disponíveis. Você pode usar objetos Java para simplificar a leitura e a gravação no banco de dados.

A seguir está um exemplo de como você pode salvar as pontuações mais altas de um jogo.

Analisar
@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();
Base de fogo
// 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 obter mais detalhes, consulte o guia Ler e gravar dados no Android .

Relações 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 separados, para que possam ser baixados com eficiência em chamadas separadas.

A seguir está um exemplo de como você pode estruturar o relacionamento entre postagens em um aplicativo de blog e seus autores.

Analisar
// 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();
Base de fogo
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 seguinte layout de dados é o 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 obter mais detalhes, consulte o guia Estruture seu banco de dados .

Lendo dados

No Parse você lê dados usando o ID de um objeto Parse específico ou executando consultas usando ParseQuery .

No Firebase, você recupera dados anexando um listener assíncrono a uma referência de banco de dados. O ouvinte é acionado uma vez para o estado inicial dos dados e novamente quando os dados são alterados, portanto, você não precisará adicionar nenhum código para determinar se os dados foram alterados.

A seguir está um exemplo de como você pode recuperar pontuações de um determinado jogador, com base no exemplo apresentado na seção "Objetos" .

Analisar
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());
        }
    }
});
Base de fogo
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 obter mais detalhes sobre os tipos de ouvintes de eventos disponíveis e sobre como solicitar e filtrar dados, consulte o guia Ler e gravar dados no Android .

Estratégia de migração sugerida

Repense 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 sua migração é considerar quais alterações seus dados exigem, incluindo:

  • Como seus objetos Parse devem ser mapeados para os dados do Firebase
  • Se você tiver relações pai-filho, como dividir seus dados em diferentes caminhos para que possam ser baixados com eficiência em chamadas separadas.

Migre seus dados

Depois de decidir como estruturar seus dados no Firebase, você precisa planejar como lidar com o período durante o qual seu aplicativo precisa gravar em ambos os bancos de dados. Suas escolhas são:

Sincronização em segundo plano

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

  • Converta seus dados de análise existentes na nova estrutura do Firebase e grave-os no Firebase Realtime Database.
  • Escreva funções do Parse Cloud Code que usam a API REST do Firebase para gravar no Firebase Realtime Database alterações feitas nos dados do Parse por clientes antigos.
  • Escreva e implante código que detecte alterações no Firebase e as sincronize com o banco de dados Parse.

Este cenário garante uma separação clara entre código antigo e novo e mantém os clientes simples. Os desafios deste cenário são lidar com grandes conjuntos de dados na exportação inicial e garantir que a sincronização bidirecional não gere recursão infinita.

Gravação Dupla

Nesse cenário, você escreve uma nova versão do aplicativo que usa Firebase e Parse, usando o Parse Cloud Code para sincronizar alterações feitas por clientes antigos do Parse Data para o Firebase Realtime Database. Quando um número suficiente de pessoas tiver migrado da versão somente Parse do aplicativo, você poderá remover o código Parse da versão de gravação dupla.

Este cenário não requer nenhum código do lado do servidor. Suas desvantagens são que os dados que não são acessados ​​não são migrados e que o tamanho do seu aplicativo aumenta com o uso de ambos os SDKs.

Autenticação Firebase

O Firebase Authentication pode autenticar usuários usando senhas e provedores de identidade federados populares, como Google, Facebook e Twitter. Ele também fornece bibliotecas de UI para economizar o investimento significativo necessário para implementar e manter uma experiência de autenticação completa para seu aplicativo em todas as plataformas.

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

Diferenças com análise de autenticação

O Parse fornece uma classe de usuário especializada chamada ParseUser que gerencia automaticamente a funcionalidade necessária para o gerenciamento de contas de usuário. ParseUser é uma subclasse de ParseObject , o que significa que os dados do usuário estão disponíveis em Parse Data e podem ser estendidos com campos adicionais como qualquer outro ParseObject .

Um FirebaseUser possui um conjunto fixo de propriedades básicas (um ID exclusivo, um endereço de e-mail principal, um nome e um URL de foto) armazenados em um banco de dados de usuários do projeto separado; essas propriedades podem ser atualizadas pelo usuário. Você não pode adicionar outras propriedades diretamente ao objeto FirebaseUser ; em vez disso, você pode armazenar as propriedades adicionais no Firebase Realtime Database.

A seguir está um exemplo de como você pode inscrever um usuário e adicionar um campo adicional de número de telefone.

Analisar
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
        }
    }
});
Base de fogo
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());
            }
        }
    });

Estratégia de migração sugerida

Migrar contas

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

Primeiro, exporte seu banco de dados de usuário do console do Parse ou de seu banco de dados auto-hospedado. Por exemplo, um arquivo JSON exportado 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 no 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 em base64 os valores bcryptPassword do Parse e use-os no campo passwordHash . Por 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 fim, 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 para seus usuários, poderá migrá-los para o Firebase Realtime Database usando as estratégias descritas na seção de migração de dados . Se você migrar contas usando o fluxo descrito na seção de migração de contas , suas contas do Firebase terão os mesmos IDs das contas do Parse, permitindo migrar e reproduzir facilmente quaisquer relações codificadas pelo ID do usuário.

Mensagens na nuvem do Firebase

Firebase Cloud Messaging (FCM) é uma solução de mensagens multiplataforma que permite entregar mensagens e notificações de maneira confiável e sem nenhum custo. O compositor de notificações é um serviço gratuito criado no Firebase Cloud Messaging que permite notificações de usuário direcionadas para desenvolvedores de aplicativos móveis.

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

Diferenças com análise de notificações push

Cada aplicativo Parse instalado em um dispositivo registrado para notificações possui um objeto Installation associado, onde você armazena todos os dados necessários para direcionar as notificações. Installation é uma subclasse de ParseUser , o que significa que você pode adicionar quaisquer dados adicionais que desejar às suas instâncias Installation .

O compositor de Notificações fornece segmentos de usuários predefinidos com base em informações como aplicativo, versão do aplicativo e idioma do dispositivo. Você pode criar segmentos de usuários mais complexos usando eventos e propriedades do Google Analytics para construir públicos. Consulte o guia de ajuda do público para saber mais. Essas informações de segmentação não são visíveis no Firebase Realtime Database.

Estratégia de migração sugerida

Migrando tokens de dispositivos

No momento em que este artigo foi escrito, o Parse Android SDK usava uma versão mais antiga dos tokens de registro do FCM, não compatível com os recursos oferecidos pelo compositor de Notificações.

Você pode obter um novo token adicionando o SDK do FCM ao seu aplicativo; no entanto, isso pode invalidar o token usado pelo Parse SDK para receber notificações. Se quiser evitar isso, você pode configurar o SDK do Parse para usar o ID do remetente do Parse e o seu ID do remetente. Dessa forma, você não invalida o token usado pelo Parse SDK, mas esteja ciente de que esta solução alternativa deixará de funcionar quando o Parse encerrar seu projeto.

Migrando canais para tópicos FCM

Se você estiver usando canais do Parse para enviar notificações, poderá migrar para tópicos do FCM, que fornecem o mesmo modelo editor-assinante. Para lidar com a transição do Parse para o FCM, você pode escrever uma nova versão do aplicativo que usa o Parse SDK para cancelar a assinatura dos canais do Parse e o FCM SDK para assinar os tópicos correspondentes do FCM. Nesta versão do app você deve desabilitar o recebimento de notificações no Parse SDK, removendo o seguinte do manifesto do seu 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 exemplo, se o seu usuário estiver inscrito no tópico "Gigantes", você faria 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 esta estratégia, você pode enviar mensagens tanto para o canal Parse quanto para o tópico FCM correspondente, oferecendo suporte a usuários de versões antigas e novas. Quando um número suficiente de usuários tiver migrado da versão somente Parse do aplicativo, você poderá desativar essa versão e começar a enviar usando apenas o FCM.

Consulte a documentação sobre tópicos do FCM para saber mais.

Configuração remota do Firebase

O Firebase Remote Config é um serviço em nuvem que permite alterar o comportamento e a aparência do seu aplicativo sem exigir que os usuários baixem uma atualização do aplicativo. Ao usar o Configuração remota, você cria valores padrão no aplicativo que controlam o comportamento e a aparência do seu aplicativo. Posteriormente, você poderá usar o Console do Firebase para substituir os valores padrão no aplicativo para todos os usuários do aplicativo ou para segmentos da sua base de usuários.

O Firebase Remote Config pode ser muito útil durante suas migrações nos casos em que você deseja testar soluções diferentes e poder transferir dinamicamente mais clientes para um provedor diferente. Por exemplo, se você tiver uma versão do seu aplicativo que usa o Firebase e o Parse para os dados, poderá usar uma regra de percentil aleatório para determinar quais clientes leem do Firebase e aumentar gradualmente a porcentagem.

Para saber mais sobre a Configuração remota do Firebase, consulte a introdução da Configuração remota .

Diferenças com configuração de análise

Com o Parse config, você pode adicionar pares chave/valor ao seu aplicativo no painel do Parse Config e, em seguida, buscar o ParseConfig no cliente. Cada instância ParseConfig obtida é sempre imutável. Quando você recuperar um novo ParseConfig da rede no futuro, ele não modificará nenhuma instância existente ParseConfig , mas criará uma nova e a disponibilizará via getCurrentConfig() .

Com a Configuração remota do Firebase, você cria padrões no aplicativo para pares de chave/valor que podem ser substituídos no Console do Firebase e pode usar regras e condições para fornecer variações na experiência do usuário do seu aplicativo para diferentes segmentos da sua base de usuários. O Firebase Remote Config implementa uma classe singleton que disponibiliza os pares chave/valor para seu aplicativo. Inicialmente o singleton retorna os valores padrão que você define no aplicativo. Você pode buscar um novo conjunto de valores do servidor a qualquer momento conveniente para seu aplicativo; depois que o novo conjunto for obtido com sucesso, você poderá escolher quando ativá-lo para disponibilizar os novos valores para o aplicativo.

Estratégia de migração sugerida

Você pode migrar para a Configuração remota do Firebase copiando os pares chave/valor da configuração do Parse para o console do Firebase e, em seguida, implantando uma nova versão do aplicativo que usa a Configuração remota do Firebase.

Se quiser experimentar o Parse Config e o Firebase Remote Config, você pode implantar uma nova versão do aplicativo que use os dois SDKs até que um número suficiente de usuários tenha migrado da versão somente do Parse.

Comparação de códigos

Analisar

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

Base de fogo

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