Confira tudo que foi anunciado no Firebase Summit e veja como usar o Firebase para acelerar o desenvolvimento de apps e executar os aplicativos com confiança. Saiba mais

Salvando dados

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Este documento aborda os quatro métodos de gravação de dados no Firebase Realtime Database: configuração, atualização, push e suporte a transações.

Formas de salvar dados

definir Grave ou substitua dados em um caminho definido , como messages/users/<username>
atualizar Atualize algumas das chaves para um caminho definido sem substituir todos os dados
Empurre Adicionar a uma lista de dados no banco de dados. Toda vez que você coloca um novo nó em uma lista, seu banco de dados gera uma chave exclusiva, como messages/users/<unique-user-id>/<username>
transação Use transações ao trabalhar com dados complexos que podem ser corrompidos por atualizações simultâneas

Salvando dados

A operação básica de gravação do banco de dados é um conjunto que salva novos dados na referência de banco de dados especificada, substituindo quaisquer dados existentes nesse caminho. Para entender o conjunto, criaremos um aplicativo de blog simples. Os dados do seu aplicativo são armazenados nesta referência de banco de dados:

Java
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
// Import Admin SDK
const { getDatabase } = require('firebase-admin/database');

// Get a database reference to our blog
const db = getDatabase();
const ref = db.ref('server/saving-data/fireblog');
Pitão
# Import database module.
from firebase_admin import db

# Get a database reference to our blog.
ref = db.reference('server/saving-data/fireblog')
Vai
// Create a database client from App.
client, err := app.Database(ctx)
if err != nil {
	log.Fatalln("Error initializing database client:", err)
}

// Get a database reference to our blog.
ref := client.NewRef("server/saving-data/fireblog")

Vamos começar salvando alguns dados do usuário. Armazenaremos cada usuário por um nome de usuário exclusivo e também armazenaremos seu nome completo e data de nascimento. Como cada usuário terá um nome de usuário exclusivo, faz sentido usar o método set aqui em vez do método push, pois você já possui a chave e não precisa criar uma.

Primeiro, crie uma referência de banco de dados para seus dados de usuário. Em seguida, use set() / setValue() para salvar um objeto de usuário no banco de dados com o nome de usuário, nome completo e aniversário do usuário. Você pode definir uma string, número, booleano, null , array ou qualquer objeto JSON. Passar null removerá os dados no local especificado. Nesse caso, você passará um objeto:

Java
public static class User {

  public String date_of_birth;
  public String full_name;
  public String nickname;

  public User(String dateOfBirth, String fullName) {
    // ...
  }

  public User(String dateOfBirth, String fullName, String nickname) {
    // ...
  }

}

DatabaseReference usersRef = ref.child("users");

Map<String, User> users = new HashMap<>();
users.put("alanisawesome", new User("June 23, 1912", "Alan Turing"));
users.put("gracehop", new User("December 9, 1906", "Grace Hopper"));

usersRef.setValueAsync(users);
Node.js
const usersRef = ref.child('users');
usersRef.set({
  alanisawesome: {
    date_of_birth: 'June 23, 1912',
    full_name: 'Alan Turing'
  },
  gracehop: {
    date_of_birth: 'December 9, 1906',
    full_name: 'Grace Hopper'
  }
});
Pitão
users_ref = ref.child('users')
users_ref.set({
    'alanisawesome': {
        'date_of_birth': 'June 23, 1912',
        'full_name': 'Alan Turing'
    },
    'gracehop': {
        'date_of_birth': 'December 9, 1906',
        'full_name': 'Grace Hopper'
    }
})
Vai

// User is a json-serializable type.
type User struct {
	DateOfBirth string `json:"date_of_birth,omitempty"`
	FullName    string `json:"full_name,omitempty"`
	Nickname    string `json:"nickname,omitempty"`
}

usersRef := ref.Child("users")
err := usersRef.Set(ctx, map[string]*User{
	"alanisawesome": {
		DateOfBirth: "June 23, 1912",
		FullName:    "Alan Turing",
	},
	"gracehop": {
		DateOfBirth: "December 9, 1906",
		FullName:    "Grace Hopper",
	},
})
if err != nil {
	log.Fatalln("Error setting value:", err)
}

Quando um objeto JSON é salvo no banco de dados, as propriedades do objeto são mapeadas automaticamente para os locais filho do banco de dados de maneira aninhada. Agora, se você navegar para a URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name , veremos o valor "Alan Turing". Você também pode salvar dados diretamente em um local filho:

Java
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing"));
usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Node.js
const usersRef = ref.child('users');
usersRef.child('alanisawesome').set({
  date_of_birth: 'June 23, 1912',
  full_name: 'Alan Turing'
});
usersRef.child('gracehop').set({
  date_of_birth: 'December 9, 1906',
  full_name: 'Grace Hopper'
});
Pitão
users_ref.child('alanisawesome').set({
    'date_of_birth': 'June 23, 1912',
    'full_name': 'Alan Turing'
})
users_ref.child('gracehop').set({
    'date_of_birth': 'December 9, 1906',
    'full_name': 'Grace Hopper'
})
Vai
if err := usersRef.Child("alanisawesome").Set(ctx, &User{
	DateOfBirth: "June 23, 1912",
	FullName:    "Alan Turing",
}); err != nil {
	log.Fatalln("Error setting value:", err)
}

if err := usersRef.Child("gracehop").Set(ctx, &User{
	DateOfBirth: "December 9, 1906",
	FullName:    "Grace Hopper",
}); err != nil {
	log.Fatalln("Error setting value:", err)
}

Os dois exemplos acima - escrever ambos os valores ao mesmo tempo como um objeto e gravá-los separadamente em locais filhos - resultará no mesmo dado sendo salvo em seu banco de dados:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper"
    }
  }
}

O primeiro exemplo acionará apenas um evento nos clientes que estão observando os dados, enquanto o segundo exemplo acionará dois. É importante observar que, se os dados já existissem em usersRef , a primeira abordagem os substituiria, mas o segundo método apenas modificaria o valor de cada nó filho separado, deixando os outros filhos de usersRef inalterados.

Atualizando dados salvos

Se você deseja gravar em vários filhos de um local de banco de dados ao mesmo tempo sem sobrescrever outros nós filhos, pode usar o método de atualização conforme mostrado abaixo:

Java
DatabaseReference hopperRef = usersRef.child("gracehop");
Map<String, Object> hopperUpdates = new HashMap<>();
hopperUpdates.put("nickname", "Amazing Grace");

hopperRef.updateChildrenAsync(hopperUpdates);
Node.js
const usersRef = ref.child('users');
const hopperRef = usersRef.child('gracehop');
hopperRef.update({
  'nickname': 'Amazing Grace'
});
Pitão
hopper_ref = users_ref.child('gracehop')
hopper_ref.update({
    'nickname': 'Amazing Grace'
})
Vai
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

Isso atualizará os dados de Grace para incluir seu apelido. Se você tivesse usado set here em vez de update, ele teria excluído full_name e date_of_birth de seu hopperRef .

O Firebase Realtime Database também oferece suporte a atualizações de vários caminhos. Isso significa que a atualização agora pode atualizar valores em vários locais em seu banco de dados ao mesmo tempo, um recurso poderoso que permite ajudá-lo a desnormalizar seus dados . Usando atualizações de vários caminhos, você pode adicionar apelidos para Grace e Alan ao mesmo tempo:

Java
Map<String, Object> userUpdates = new HashMap<>();
userUpdates.put("alanisawesome/nickname", "Alan The Machine");
userUpdates.put("gracehop/nickname", "Amazing Grace");

usersRef.updateChildrenAsync(userUpdates);
Node.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome/nickname': 'Alan The Machine',
  'gracehop/nickname': 'Amazing Grace'
});
Pitão
users_ref.update({
    'alanisawesome/nickname': 'Alan The Machine',
    'gracehop/nickname': 'Amazing Grace'
})
Vai
if err := usersRef.Update(ctx, map[string]interface{}{
	"alanisawesome/nickname": "Alan The Machine",
	"gracehop/nickname":      "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating children:", err)
}

Após esta atualização, Alan e Grace tiveram seus apelidos adicionados:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing",
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper",
      "nickname": "Amazing Grace"
    }
  }
}

Observe que tentar atualizar objetos escrevendo objetos com os caminhos incluídos resultará em um comportamento diferente. Vamos dar uma olhada no que acontece se você tentar atualizar Grace e Alan desta forma:

Java
Map<String, Object> userNicknameUpdates = new HashMap<>();
userNicknameUpdates.put("alanisawesome", new User(null, null, "Alan The Machine"));
userNicknameUpdates.put("gracehop", new User(null, null, "Amazing Grace"));

usersRef.updateChildrenAsync(userNicknameUpdates);
Node.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome': {
    'nickname': 'Alan The Machine'
  },
  'gracehop': {
    'nickname': 'Amazing Grace'
  }
});
Pitão
users_ref.update({
    'alanisawesome': {
        'nickname': 'Alan The Machine'
    },
    'gracehop': {
        'nickname': 'Amazing Grace'
    }
})
Vai
if err := usersRef.Update(ctx, map[string]interface{}{
	"alanisawesome": &User{Nickname: "Alan The Machine"},
	"gracehop":      &User{Nickname: "Amazing Grace"},
}); err != nil {
	log.Fatalln("Error updating children:", err)
}

Isso resulta em um comportamento diferente, ou seja, sobrescrevendo todo o nó /users :

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}

Adicionando um retorno de chamada de conclusão

Em Node.js e Java Admin SDKs, se quiser saber quando seus dados foram confirmados, você pode adicionar um retorno de chamada de conclusão. Os métodos set e update nesses SDKs recebem um retorno de chamada de conclusão opcional que é chamado quando a gravação é confirmada no banco de dados. Se a chamada não foi bem-sucedida por algum motivo, o retorno de chamada recebe um objeto de erro indicando o motivo da falha. Em Python e Go Admin SDKs, todos os métodos de gravação são bloqueadores. Ou seja, os métodos de gravação não retornam até que as gravações sejam confirmadas no banco de dados.

Java
DatabaseReference dataRef = ref.child("data");
dataRef.setValue("I'm writing data", new DatabaseReference.CompletionListener() {
  @Override
  public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
    if (databaseError != null) {
      System.out.println("Data could not be saved " + databaseError.getMessage());
    } else {
      System.out.println("Data saved successfully.");
    }
  }
});
Node.js
dataRef.set('I\'m writing data', (error) => {
  if (error) {
    console.log('Data could not be saved.' + error);
  } else {
    console.log('Data saved successfully.');
  }
});

Salvando listas de dados

Ao criar listas de dados, é importante ter em mente a natureza multiusuário da maioria dos aplicativos e ajustar a estrutura da lista de acordo. Expandindo o exemplo acima, vamos adicionar postagens de blog ao seu aplicativo. Seu primeiro instinto pode ser usar set para armazenar filhos com índices inteiros de incremento automático, como o seguinte:

// NOT RECOMMENDED - use push() instead!
{
  "posts": {
    "0": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "1": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Se um usuário adicionar uma nova postagem, ela será armazenada como /posts/2 . Isso funcionaria se apenas um único autor estivesse adicionando postagens, mas em seu aplicativo de blog colaborativo muitos usuários podem adicionar postagens ao mesmo tempo. Se dois autores escreverem em /posts/2 simultaneamente, um dos posts será deletado pelo outro.

Para resolver isso, os clientes Firebase fornecem uma função push() que gera uma chave exclusiva para cada novo filho . Ao usar chaves filhas exclusivas, vários clientes podem adicionar filhas ao mesmo local ao mesmo tempo sem se preocupar com conflitos de gravação.

Java
public static class Post {

  public String author;
  public String title;

  public Post(String author, String title) {
    // ...
  }

}

DatabaseReference postsRef = ref.child("posts");

DatabaseReference newPostRef = postsRef.push();
newPostRef.setValueAsync(new Post("gracehop", "Announcing COBOL, a New Programming Language"));

// We can also chain the two calls together
postsRef.push().setValueAsync(new Post("alanisawesome", "The Turing Machine"));
Node.js
const newPostRef = postsRef.push();
newPostRef.set({
  author: 'gracehop',
  title: 'Announcing COBOL, a New Programming Language'
});

// we can also chain the two calls together
postsRef.push().set({
  author: 'alanisawesome',
  title: 'The Turing Machine'
});
Pitão
posts_ref = ref.child('posts')

new_post_ref = posts_ref.push()
new_post_ref.set({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})

# We can also chain the two calls together
posts_ref.push().set({
    'author': 'alanisawesome',
    'title': 'The Turing Machine'
})
Vai

// Post is a json-serializable type.
type Post struct {
	Author string `json:"author,omitempty"`
	Title  string `json:"title,omitempty"`
}

postsRef := ref.Child("posts")

newPostRef, err := postsRef.Push(ctx, nil)
if err != nil {
	log.Fatalln("Error pushing child node:", err)
}

if err := newPostRef.Set(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error setting value:", err)
}

// We can also chain the two calls together
if _, err := postsRef.Push(ctx, &Post{
	Author: "alanisawesome",
	Title:  "The Turing Machine",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

A chave exclusiva é baseada em um registro de data e hora, portanto, os itens da lista serão automaticamente ordenados em ordem cronológica. Como o Firebase gera uma chave exclusiva para cada postagem do blog, nenhum conflito de gravação ocorrerá se vários usuários adicionarem uma postagem ao mesmo tempo. Seus dados de banco de dados agora se parecem com isso:

{
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "-JRHTHaKuITFIhnj02kE": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Em JavaScript, Python e Go, o padrão de chamar push() e imediatamente chamar set() é tão comum que o Firebase SDK permite combiná-los passando os dados a serem configurados diretamente para push() da seguinte forma:

Java
// No Java equivalent
Node.js
// This is equivalent to the calls to push().set(...) above
postsRef.push({
  author: 'gracehop',
  title: 'Announcing COBOL, a New Programming Language'
});;
Pitão
# This is equivalent to the calls to push().set(...) above
posts_ref.push({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})
Vai
if _, err := postsRef.Push(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

Obtendo a chave única gerada por push()

Chamar push() retornará uma referência ao novo caminho de dados, que você pode usar para obter a chave ou definir dados para ela. O código a seguir resultará nos mesmos dados do exemplo acima, mas agora teremos acesso à chave única que foi gerada:

Java
// Generate a reference to a new location and add some data using push()
DatabaseReference pushedPostRef = postsRef.push();

// Get the unique ID generated by a push()
String postId = pushedPostRef.getKey();
Node.js
// Generate a reference to a new location and add some data using push()
const newPostRef = postsRef.push();

// Get the unique key generated by push()
const postId = newPostRef.key;
Pitão
# Generate a reference to a new location and add some data using push()
new_post_ref = posts_ref.push()

# Get the unique key generated by push()
post_id = new_post_ref.key
Vai
// Generate a reference to a new location and add some data using Push()
newPostRef, err := postsRef.Push(ctx, nil)
if err != nil {
	log.Fatalln("Error pushing child node:", err)
}

// Get the unique key generated by Push()
postID := newPostRef.Key

Como você pode ver, você pode obter o valor da chave exclusiva de sua referência push() .

Na próxima seção, Recuperando dados , aprenderemos como ler esses dados de um banco de dados Firebase.

Salvando Dados Transacionais

Ao trabalhar com dados complexos que podem ser corrompidos por modificações simultâneas, como contadores incrementais, o SDK fornece uma operação de transação .

Em Java e Node.js, você fornece à operação de transação dois retornos de chamada: uma função de atualização e um retorno de chamada de conclusão opcional. Em Python e Go, a operação de transação é bloqueante e, portanto, aceita apenas a função de atualização.

A função de atualização leva o estado atual dos dados como um argumento e deve retornar o novo estado desejado que você gostaria de escrever. Por exemplo, se você quiser aumentar o número de votos positivos em uma postagem de blog específica, escreva uma transação como a seguinte:

Java
DatabaseReference upvotesRef = ref.child("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.runTransaction(new Transaction.Handler() {
  @Override
  public Transaction.Result doTransaction(MutableData mutableData) {
    Integer currentValue = mutableData.getValue(Integer.class);
    if (currentValue == null) {
      mutableData.setValue(1);
    } else {
      mutableData.setValue(currentValue + 1);
    }

    return Transaction.success(mutableData);
  }

  @Override
  public void onComplete(
      DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) {
    System.out.println("Transaction completed");
  }
});
Node.js
const upvotesRef = db.ref('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes');
upvotesRef.transaction((current_value) => {
  return (current_value || 0) + 1;
});
Pitão
def increment_votes(current_value):
    return current_value + 1 if current_value else 1

upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes')
try:
    new_vote_count = upvotes_ref.transaction(increment_votes)
    print('Transaction completed')
except db.TransactionAbortedError:
    print('Transaction failed to commit')
Vai
fn := func(t db.TransactionNode) (interface{}, error) {
	var currentValue int
	if err := t.Unmarshal(&currentValue); err != nil {
		return nil, err
	}
	return currentValue + 1, nil
}

ref := client.NewRef("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes")
if err := ref.Transaction(ctx, fn); err != nil {
	log.Fatalln("Transaction failed to commit:", err)
}

O exemplo acima verifica se o contador é null ou se ainda não foi incrementado, pois as transações podem ser chamadas com null se nenhum valor padrão foi gravado.

Se o código acima tivesse sido executado sem uma função de transação e dois clientes tentassem incrementá-lo simultaneamente, ambos escreveriam 1 como o novo valor, resultando em um incremento em vez de dois.

Conectividade de rede e gravações off-line

Os clientes Firebase Node.js e Java mantêm sua própria versão interna de todos os dados ativos. Quando os dados são gravados, eles são gravados primeiro nesta versão local. O cliente então sincroniza esses dados com o banco de dados e com outros clientes com base no 'melhor esforço'.

Como resultado, todas as gravações no banco de dados acionarão eventos locais imediatamente, antes mesmo de qualquer dado ser gravado no banco de dados. Isso significa que quando você escreve um aplicativo usando o Firebase, seu aplicativo permanecerá responsivo, independentemente da latência da rede ou da conectividade com a Internet.

Depois que a conectividade for restabelecida, receberemos o conjunto apropriado de eventos para que o cliente "recupere" o estado atual do servidor, sem precisar escrever nenhum código personalizado.

Protegendo seus dados

O Firebase Realtime Database possui uma linguagem de segurança que permite definir quais usuários têm acesso de leitura e gravação a diferentes nós de seus dados. Você pode ler mais sobre isso em Proteja seus dados .