Ir para o console

Ler e gravar dados no Android

Neste documento você encontra noções básicas sobre leitura e gravação de dados do Firebase.

Os dados do Firebase são gravados em uma referência FirebaseDatabase e recuperados ao anexar um listener assíncrono à referência. O listener é acionado uma vez para o estado inicial dos dados e novamente quando os dados são alterados.

Receber uma referência do banco de dados

Para ler ou gravar dados no banco de dados, você precisa de uma instância de DatabaseReference:

Java

private DatabaseReference mDatabase;// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Kotlin

private lateinit var database: DatabaseReference// ...
database = FirebaseDatabase.getInstance().reference

Ler e gravar dados

Operações básicas de gravação

Para operações de gravação básicas, você pode usar setValue() para salvar dados em uma referência especificada, substituindo os dados existentes no caminho em questão. Você pode usar esse método para:

  • passar os tipos que correspondam aos tipos JSON disponíveis:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • passar um objeto Java personalizado se a classe que o define tiver um construtor padrão que não recebe argumentos e tem getters públicos para as propriedades a serem atribuídas.

Se você usar um objeto Java, o conteúdo desse objeto será automaticamente mapeado para locais filhos de maneira aninhada. Usar um objeto Java normalmente torna seu código mais legível e fácil de manter. Por exemplo, se você tiver um app com um perfil básico de usuário, o objeto User poderá ter a seguinte aparência:

Java

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}

Kotlin

@IgnoreExtraProperties
data class User(
    var username: String? = "",
    var email: String? = ""
)

Você pode adicionar um usuário com setValue() da seguinte forma:

Java

private void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

Kotlin

private fun writeNewUser(userId: String, name: String, email: String?) {
    val user = User(name, email)
    database.child("users").child(userId).setValue(user)
}

Usar setValue() dessa forma substituirá os dados no local especificado, incluindo qualquer node filho. No entanto, ainda é possível atualizar um filho sem substituir todo o objeto. Para que os usuários atualizem os próprios perfis, atualize o nome deles desta forma:

Java

mDatabase.child("users").child(userId).child("username").setValue(name);

Kotlin

database.child("users").child(userId).child("username").setValue(name)

Detectar eventos de valor

Para ler dados em um caminho e detectar alterações, use o método addValueEventListener() ou addListenerForSingleValueEvent() para adicionar ValueEventListener a um DatabaseReference.

Listener Retorno de chamada de evento Uso normal
ValueEventListener onDataChange() Ler e detectar alterações em todo o conteúdo de um caminho.

Você pode usar o método onDataChange() para ler um instantâneo estático do conteúdo de um determinado caminho, pois ele existe no momento do evento. Esse método será acionado uma vez quando o listener for anexado e sempre que os dados forem alterados, incluindo os filhos. O retorno de chamada do evento recebe um instantâneo que contém todos os dados no local, incluindo dados filhos. Se não houver dados, o instantâneo retornará false quando você chamar exists() e null quando você chamar getValue() nele.

O exemplo a seguir mostra um app de blog social recuperando detalhes de uma postagem do banco de dados:

Java

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
};
mPostReference.addValueEventListener(postListener);

Kotlin

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue(Post::class.java)
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
}
postReference.addValueEventListener(postListener)

O listener recebe um DataSnapshot que contém os dados no local especificado no banco de dados no momento do evento. Chamar getValue() em um instantâneo retorna a representação do objeto Java dos dados. Se não houver dados no local, chamar getValue() retorna null.

Neste exemplo, ValueEventListener também define o método onCancelled() que é chamado se a leitura for cancelada. Por exemplo, uma leitura pode ser cancelada se o cliente não tiver permissão de leitura desse local no banco de dados do Firebase. Este método recebe um objeto DatabaseError indicando por que a falha ocorreu.

Ler dados uma vez

Em alguns casos, você pode querer que um retorno de chamada seja iniciado uma vez e depois seja removido imediatamente, como ao inicializar um elemento de IU que não será alterado. Você pode usar o método addListenerForSingleValueEvent() para simplificar esse cenário: ele é acionado uma vez apenas.

Isso é útil para dados que só precisam ser carregados uma vez e que não serão alterados com frequência nem precisarão ser detectados ativamente. Por exemplo, o app de blog dos exemplos anteriores usa esse método para carregar o perfil de um usuário quando ele começa a escrever uma nova postagem:

Atualização ou exclusão de dados

Atualizar campos específicos

Para gravar em filhos específicos de um node simultaneamente sem substituir outros nodes filhos, use o método updateChildren().

Ao chamar updateChildren(), atualize valores de filhos de nível inferior. Para isso, basta especificar um caminho para a chave. Se os dados forem armazenados em vários locais para aprimorar a escalabilidade, você poderá atualizar todas as instâncias desses dados usando a distribuição de dados. Por exemplo, um app de blog social poderia ter uma classe Post como esta:

Java

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }

}

Kotlin

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap()
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
                "uid" to uid,
                "author" to author,
                "title" to title,
                "body" to body,
                "starCount" to starCount,
                "stars" to stars
        )
    }
}

Para criar uma postagem e atualizá-la simultaneamente no feed de atividades recentes e no feed de atividades do usuário que fez a postagem, o aplicativo de blog usa códigos como estes:

Java

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

Kotlin

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = HashMap<String, Any>()
    childUpdates["/posts/$key"] = postValues
    childUpdates["/user-posts/$userId/$key"] = postValues

    database.updateChildren(childUpdates)
}

Esse exemplo usa push() para criar uma postagem no node que armazena as postagens para todos os usuários em /posts/$postid e, simultaneamente, recuperar a chave com getKey(). A chave pode ser usada para criar uma segunda entrada nas postagens do usuário em /user-posts/$userid/$postid.

Usando esses caminhos, você pode realizar atualizações simultâneas em vários locais da árvore JSON com uma só chamada para updateChildren(), por exemplo, criando uma nova postagem nos dois locais. Essas atualizações são atômicas: ou todas funcionam ou todas falham.

Adicionar um retorno de chamada de conclusão

Para saber quando seus dados foram confirmados, é possível adicionar um listener de conclusão. Ambos setValue() e updateChildren() recebem um retorno de listener de conclusão opcional, que será chamado quando a gravação for confirmada no banco de dados. Se a chamada não for bem-sucedida, o listener receberá um objeto de erro indicando o motivo da falha.

Java

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

Kotlin

database.child("users").child(userId).setValue(user)
        .addOnSuccessListener {
            // Write was successful!
            // ...
        }
        .addOnFailureListener {
            // Write failed
            // ...
        }

Excluir dados

A maneira mais simples de excluir dados é chamar removeValue() em uma referência ao local dos dados em questão.

Você também pode excluir especificando null como o valor para outra operação de gravação, como setValue() ou updateChildren(). Você pode usar essa técnica com updateChildren() para excluir vários nós filhos em uma única chamada de API.

Desanexar listeners

Retornos de chamada são removidos chamando o método removeEventListener() na sua referência ao banco de dados do Firebase.

Se um listener tiver sido adicionado várias vezes em um local de dados, ele será chamado várias vezes para cada evento, e você precisa desanexá-lo o mesmo número de vezes para removê-lo por completo.

Chamar removeEventListener() em um listener pai não remove automaticamente os listeners registrados nos seus nodes filhos. removeEventListener() também precisa ser chamado nos listeners filhos para remover o retorno de chamada.

Salvar dados como transações

Ao trabalhar com dados que podem ser corrompidos por modificações simultâneas, como contadores incrementais, use uma operação de transação. Essa operação aceita dois argumentos: uma função de atualização e um retorno de chamada de conclusão opcional. A função de atualização usa o estado atual dos dados como um argumento e retorna o novo estado desejado de acordo com suas preferências. Se outro cliente fizer uma gravação no local antes que seu novo valor seja gravado com sucesso, sua função de atualização será chamada novamente com o novo valor atual e a gravação será repetida.

No app de blog social do exemplo, os usuários podem adicionar ou remover estrelas de postagens e acompanhar quantas foram recebidas desta forma:

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean b,
                               DataSnapshot dataSnapshot) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

Kotlin

private fun onStarClicked(postRef: DatabaseReference) {
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                    ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
            databaseError: DatabaseError?,
            b: Boolean,
            dataSnapshot: DataSnapshot?
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

A transação impede uma contagem incorreta de estrelas se vários usuários adicionam simultaneamente estrelas à mesma postagem ou se o cliente tem dados desatualizados. Se a transação for rejeitada, o servidor retornará o valor atual ao cliente, que executará a transação novamente com o valor atualizado. Isso se repetirá até que a transação seja aceita ou até que muitas tentativas sejam realizadas.

Gravar dados off-line

Se um cliente perder a conexão de rede, o app continuará funcionando.

Todos os clientes conectados a um banco de dados do Firebase mantêm a própria versão interna de dados ativos. A gravação deles ocorre primeiro nessa versão local. Depois, o cliente do Firebase sincroniza esses dados com os servidores remotos e com outros clientes de acordo com o modelo “melhor esforço".

Consequentemente, todas as gravações no banco de dados acionam eventos locais, antes de qualquer dado ser gravado no servidor, e o app continua responsivo, independentemente da conectividade ou da latência da rede.

Quando a conectividade for restabelecida, seu app recebe o conjunto apropriado de eventos para que o cliente seja sincronizado com o estado atual do servidor, sem necessidade de criar um código personalizado.

Próximas etapas