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

Ler e gravar dados em plataformas Apple

(Opcional) Protótipo e teste com Firebase Local Emulator Suite

Antes de falar sobre como seu aplicativo lê e grava no Realtime Database, vamos apresentar um conjunto de ferramentas que você pode usar para prototipar e testar a funcionalidade do Realtime Database: Firebase Local Emulator Suite. Se você está experimentando diferentes modelos de dados, otimizando suas regras de segurança ou trabalhando para encontrar a maneira mais econômica de interagir com o back-end, poder trabalhar localmente sem implantar serviços ao vivo pode ser uma ótima ideia.

Um emulador do Realtime Database faz parte do Local Emulator Suite, que permite que seu aplicativo interaja com o conteúdo e a configuração do banco de dados emulado, bem como, opcionalmente, com os recursos do projeto emulado (funções, outros bancos de dados e regras de segurança).

Usar o emulador do Realtime Database envolve apenas algumas etapas:

  1. Adicionando uma linha de código à configuração de teste do seu aplicativo para se conectar ao emulador.
  2. Na raiz do diretório local do projeto, execute firebase emulators:start .
  3. Fazer chamadas a partir do código de protótipo do seu aplicativo usando um SDK da plataforma do Realtime Database como de costume ou usando a API REST do Realtime Database.

Um passo a passo detalhado envolvendo o Realtime Database e o Cloud Functions está disponível. Você também deve dar uma olhada na introdução do Local Emulator Suite .

Obter um FIRDatabaseReference

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

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

Gravar dados

Este documento aborda os fundamentos da leitura e gravação de dados do Firebase.

Os dados do Firebase são gravados em uma referência do Database de dados e recuperados anexando um ouvinte assíncrono à referência. O ouvinte é acionado uma vez para o estado inicial dos dados e novamente sempre que os dados forem alterados.

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

Para operações básicas de gravação, você pode usar setValue para salvar dados em uma referência especificada, substituindo quaisquer dados existentes nesse caminho. Você pode usar este método para:

  • Transmita os tipos que correspondem aos tipos JSON disponíveis da seguinte maneira:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Por exemplo, você pode adicionar um usuário com setValue da seguinte forma:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

O uso de setValue dessa maneira substitui os dados no local especificado, incluindo quaisquer nós filhos. No entanto, você ainda pode atualizar um filho sem reescrever o objeto inteiro. Se você quiser permitir que os usuários atualizem seus perfis, você pode atualizar o nome de usuário da seguinte forma:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Ler dados

Ler dados ouvindo eventos de valor

Para ler os dados em um caminho e escutar as alterações, use observeEventType:withBlock de FIRDatabaseReference para observar eventos FIRDataEventTypeValue .

Tipo de evento Uso típico
FIRDataEventTypeValue Leia e ouça alterações em todo o conteúdo de um caminho.

Você pode usar o evento FIRDataEventTypeValue para ler os dados em um determinado caminho, conforme ele existe no momento do evento. Esse método é acionado uma vez quando o ouvinte é anexado e novamente toda vez que os dados, incluindo quaisquer filhos, são alterados. O retorno de chamada do evento recebe um snapshot contendo todos os dados naquele local, incluindo dados filho. Se não houver dados, o snapshot retornará false quando você chamar exists() e nil quando você ler sua propriedade value .

O exemplo a seguir demonstra um aplicativo de blog social recuperando os detalhes de uma postagem do banco de dados:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

O ouvinte recebe um FIRDataSnapshot que contém os dados no local especificado no banco de dados no momento do evento em sua propriedade de value . Você pode atribuir os valores ao tipo nativo apropriado, como NSDictionary . Se não houver dados no local, o value será nil .

Ler dados uma vez

Leia uma vez usando getData()

O SDK foi projetado para gerenciar interações com servidores de banco de dados, independentemente de seu aplicativo estar online ou offline.

Geralmente, você deve usar as técnicas de eventos de valor descritas acima para ler dados para ser notificado sobre atualizações nos dados do back-end. Essas técnicas reduzem o uso e o faturamento e são otimizadas para oferecer aos usuários a melhor experiência à medida que ficam online e offline.

Se você precisar dos dados apenas uma vez, poderá usar getData() para obter um instantâneo dos dados do banco de dados. Se por algum motivo getData() não puder retornar o valor do servidor, o cliente sondará o cache de armazenamento local e retornará um erro se o valor ainda não for encontrado.

O exemplo a seguir demonstra como recuperar o nome de usuário público de um usuário uma única vez do banco de dados:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
ref.child("users/\(uid)/username").getData(completion:  { error, snapshot in
  guard error == nil else {
    print(error!.localizedDescription)
    return;
  }
  let userName = snapshot.value as? String ?? "Unknown";
});

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid];
[[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) {
  if (error) {
    NSLog(@"Received an error %@", error);
    return;
  }
  NSString *userName = snapshot.value;
}];

O uso desnecessário de getData() pode aumentar o uso da largura de banda e levar à perda de desempenho, o que pode ser evitado usando um ouvinte em tempo real, conforme mostrado acima.

Leia os dados uma vez com um observador

Em alguns casos, você pode querer que o valor do cache local seja retornado imediatamente, em vez de verificar um valor atualizado no servidor. Nesses casos, você pode usar observeSingleEventOfType para obter os dados do cache de disco local imediatamente.

Isso é útil para dados que precisam ser carregados apenas uma vez e não devem ser alterados com frequência ou exigir escuta ativa. Por exemplo, o aplicativo de blog nos exemplos anteriores usa este método para carregar o perfil de um usuário quando ele começa a criar uma nova postagem:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in
  // Get user value
  let value = snapshot.value as? NSDictionary
  let username = value?["username"] as? String ?? ""
  let user = User(username: username)

  // ...
}) { error in
  print(error.localizedDescription)
}

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
NSString *userID = [FIRAuth auth].currentUser.uid;
[[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  // Get user value
  User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]];

  // ...
} withCancelBlock:^(NSError * _Nonnull error) {
  NSLog(@"%@", error.localizedDescription);
}];

Atualizando ou excluindo dados

Atualizar campos específicos

Para gravar simultaneamente em filhos específicos de um nó sem sobrescrever outros nós filhos, use o método updateChildValues .

Ao chamar updateChildValues ​​, você pode atualizar valores filho de nível inferior especificando um caminho para a chave. Se os dados forem armazenados em vários locais para dimensionar melhor, você poderá atualizar todas as instâncias desses dados usando distribuição de dados . Por exemplo, um aplicativo de blog social pode querer criar uma postagem e atualizá-la simultaneamente para o feed de atividade recente e o feed de atividade do usuário da postagem. Para fazer isso, o aplicativo de blog usa um código como este:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
guard let key = ref.child("posts").childByAutoId().key else { return }
let post = ["uid": userID,
            "author": username,
            "title": title,
            "body": body]
let childUpdates = ["/posts/\(key)": post,
                    "/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
NSString *key = [[_ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"uid": userID,
                       @"author": username,
                       @"title": title,
                       @"body": body};
NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post,
                               [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post};
[_ref updateChildValues:childUpdates];

Este exemplo usa childByAutoId para criar uma postagem no nó contendo postagens para todos os usuários em /posts/$postid e simultaneamente recuperar a chave com getKey() . A chave pode então ser usada para criar uma segunda entrada nas postagens do usuário em /user-posts/$userid/$postid .

Usando esses caminhos, você pode executar atualizações simultâneas em vários locais na árvore JSON com uma única chamada para updateChildValues ​​, como este exemplo cria a nova postagem em ambos os locais. As atualizações simultâneas feitas dessa maneira são atômicas: todas as atualizações são bem-sucedidas ou todas as atualizações falham.

Adicionar um bloco de conclusão

Se você quiser saber quando seus dados foram confirmados, você pode adicionar um bloco de conclusão. Tanto setValue quanto updateChildValues ​​usam um bloco de conclusão opcional que é chamado quando a gravação é confirmada no banco de dados. Esse ouvinte pode ser útil para acompanhar quais dados foram salvos e quais dados ainda estão sendo sincronizados. Se a chamada não for bem-sucedida, o ouvinte receberá um objeto de erro indicando o motivo da falha.

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
ref.child("users").child(user.uid).setValue(["username": username]) {
  (error:Error?, ref:DatabaseReference) in
  if let error = error {
    print("Data could not be saved: \(error).")
  } else {
    print("Data saved successfully!")
  }
}

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  if (error) {
    NSLog(@"Data could not be saved: %@", error);
  } else {
    NSLog(@"Data saved successfully.");
  }
}];

Excluir dados

A maneira mais simples de excluir dados é chamar removeValue em uma referência ao local desses dados.

Você também pode excluir especificando nil como o valor para outra operação de gravação, como setValue ou updateChildValues ​​. Você pode usar essa técnica com updateChildValues ​​para excluir vários filhos em uma única chamada de API.

Desanexar ouvintes

Os observadores não param automaticamente de sincronizar os dados quando você sai de um ViewController . Se um observador não for removido corretamente, ele continuará sincronizando os dados com a memória local. Quando um observador não for mais necessário, remova-o passando o FIRDatabaseHandle associado ao método removeObserverWithHandle .

Quando você adiciona um bloco de retorno de chamada a uma referência, um FIRDatabaseHandle é retornado. Esses identificadores podem ser usados ​​para remover o bloco de retorno de chamada.

Se vários ouvintes tiverem sido adicionados a uma referência de banco de dados, cada ouvinte será chamado quando um evento for gerado. Para interromper a sincronização de dados nesse local, você deve remover todos os observadores em um local chamando o método removeAllObservers .

Chamar removeObserverWithHandle ou removeAllObservers em um ouvinte não remove automaticamente os ouvintes registrados em seus nós filhos; você também deve acompanhar essas referências ou identificadores para removê-los.

Salvar dados como transações

Ao trabalhar com dados que podem ser corrompidos por modificações simultâneas, como contadores incrementais, você pode usar uma operação de transação . Você dá a esta operação dois argumentos: uma função de atualização e um retorno de chamada de conclusão opcional. A função de atualização pega o estado atual dos dados como um argumento e retorna o novo estado desejado que você gostaria de escrever.

Por exemplo, no exemplo de aplicativo de blog social, você pode permitir que os usuários marquem e excluam postagens com estrela e acompanhem quantas estrelas uma postagem recebeu da seguinte maneira:

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String: AnyObject],
    let uid = Auth.auth().currentUser?.uid {
    var stars: [String: Bool]
    stars = post["stars"] as? [String: Bool] ?? [:]
    var starCount = post["starCount"] as? Int ?? 0
    if let _ = stars[uid] {
      // Unstar the post and remove self from stars
      starCount -= 1
      stars.removeValue(forKey: uid)
    } else {
      // Star the post and add self to stars
      starCount += 1
      stars[uid] = true
    }
    post["starCount"] = starCount as AnyObject?
    post["stars"] = stars as AnyObject?

    // Set value and report transaction success
    currentData.value = post

    return TransactionResult.success(withValue: currentData)
  }
  return TransactionResult.success(withValue: currentData)
}) { error, committed, snapshot in
  if let error = error {
    print(error.localizedDescription)
  }
}

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
  NSMutableDictionary *post = currentData.value;
  if (!post || [post isEqual:[NSNull null]]) {
    return [FIRTransactionResult successWithValue:currentData];
  }

  NSMutableDictionary *stars = post[@"stars"];
  if (!stars) {
    stars = [[NSMutableDictionary alloc] initWithCapacity:1];
  }
  NSString *uid = [FIRAuth auth].currentUser.uid;
  int starCount = [post[@"starCount"] intValue];
  if (stars[uid]) {
    // Unstar the post and remove self from stars
    starCount--;
    [stars removeObjectForKey:uid];
  } else {
    // Star the post and add self to stars
    starCount++;
    stars[uid] = @YES;
  }
  post[@"stars"] = stars;
  post[@"starCount"] = @(starCount);

  // Set value and report transaction success
  currentData.value = post;
  return [FIRTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError * _Nullable error,
                       BOOL committed,
                       FIRDataSnapshot * _Nullable snapshot) {
  // Transaction completed
  if (error) {
    NSLog(@"%@", error.localizedDescription);
  }
}];

O uso de uma transação evita que a contagem de estrelas seja incorreta se vários usuários marcarem a mesma postagem ao mesmo tempo ou se o cliente tiver dados desatualizados. O valor contido na classe FIRMutableData é inicialmente o último valor conhecido do cliente para o caminho ou nil se não houver nenhum. O servidor compara o valor inicial com seu valor atual e aceita a transação se os valores corresponderem ou a rejeita. Se a transação for rejeitada, o servidor retorna o valor atual para o cliente, que executa novamente a transação com o valor atualizado. Isso se repete até que a transação seja aceita ou muitas tentativas tenham sido feitas.

Incrementos atômicos do lado do servidor

No caso de uso acima, estamos escrevendo dois valores no banco de dados: o ID do usuário que marca/desmarca a postagem e a contagem de estrelas incrementada. Se já sabemos que o usuário está marcando a postagem, podemos usar uma operação de incremento atômico em vez de uma transação.

Rápido

Observação: este produto Firebase não está disponível no destino App Clip.
let updates = [
  "posts/\(postID)/stars/\(userID)": true,
  "posts/\(postID)/starCount": ServerValue.increment(1),
  "user-posts/\(postID)/stars/\(userID)": true,
  "user-posts/\(postID)/starCount": ServerValue.increment(1)
] as [String : Any]
Database.database().reference().updateChildValues(updates);

Objective-C

Observação: este produto Firebase não está disponível no destino App Clip.
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1],
                        [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]};
[[[FIRDatabase database] reference] updateChildValues:updates];

Esse código não usa uma operação de transação, portanto, não é executado novamente automaticamente se houver uma atualização conflitante. No entanto, como a operação de incremento ocorre diretamente no servidor de banco de dados, não há possibilidade de conflito.

Se você deseja detectar e rejeitar conflitos específicos do aplicativo, como um usuário marcando uma postagem que ele já marcou antes, você deve escrever regras de segurança personalizadas para esse caso de uso.

Trabalhe com dados off-line

Se um cliente perder sua conexão de rede, seu aplicativo continuará funcionando corretamente.

Cada cliente conectado a um banco de dados Firebase 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 Firebase sincroniza esses dados com os servidores de banco de dados remotos e com outros clientes com base no "melhor esforço".

Como resultado, todas as gravações no banco de dados acionam eventos locais imediatamente, antes que qualquer dado seja gravado no servidor. Isso significa que seu aplicativo permanece responsivo, independentemente da latência ou conectividade da rede.

Depois que a conectividade é restabelecida, seu aplicativo recebe o conjunto apropriado de eventos para que o cliente sincronize com o estado atual do servidor, sem precisar escrever nenhum código personalizado.

Falaremos mais sobre o comportamento off-line em Saiba mais sobre os recursos on-line e off-line .

Próximos passos