Database
 sql >> Base de Dados >  >> RDS >> Database

Primeiros passos com o Cloud Firestore para iOS


Os codificadores móveis têm aproveitado a plataforma Mobile Backend as a Service (MBaaS) do Google Firebase Realtime Database há muitos anos, ajudando-os a se concentrar na criação de recursos para seus aplicativos sem precisar se preocupar com a infraestrutura de back-end e o banco de dados. Ao facilitar o armazenamento e a persistência de dados na nuvem e cuidar da autenticação e da segurança, o Firebase permite que os codificadores se concentrem no lado do cliente.


No ano passado, o Google anunciou mais uma solução de banco de dados de back-end, Cloud Firestore, criada desde o início com a promessa de maior escalabilidade e intuitividade. No entanto, isso gerou alguma confusão quanto ao seu lugar em relação ao principal produto já existente do Google, Firebase Realtime Database. Este tutorial irá descrever as diferenças entre as duas plataformas e as vantagens distintas de cada uma. Você aprenderá a trabalhar com referências de documentos do Firestore, além de ler, escrever, atualizar e excluir dados em tempo real, criando um aplicativo de lembretes simples.

Objetivos deste tutorial


Este tutorial apresentará você ao Cloud Firestore. Você aprenderá como aproveitar a plataforma para persistência e sincronização de banco de dados em tempo real. Abordaremos os seguintes tópicos:
  • o que é o Cloud Firestore
  • o modelo de dados do Firestore
  • configurando o Cloud Firestore
  • criar e trabalhar com referências do Cloud Firestore
  • lendo dados em tempo real do Cloud Firestore
  • criar, atualizar e excluir dados
  • filtragem e consultas compostas

Conhecimento presumido


Este tutorial pressupõe que você tenha tido alguma exposição ao Firebase e um histórico de desenvolvimento com Swift e Xcode.

O que é o Cloud Firestore?


Assim como o Firebase Realtime Database, o Firestore oferece aos desenvolvedores de dispositivos móveis e da Web uma solução de nuvem multiplataforma para manter os dados em tempo real, independentemente da latência da rede ou da conectividade com a Internet, além de integração perfeita com o conjunto de produtos do Google Cloud Platform. Junto com essas semelhanças, existem vantagens e desvantagens distintas que diferenciam um do outro.

Modelo de dados


Em um nível fundamental, o Realtime Database armazena dados como uma árvore JSON grande, monolítica e hierárquica, enquanto o Firestore organiza os dados em documentos e coleções, bem como em subcoleções. Isso requer menos desnormalização. Armazenar dados em uma árvore JSON tem os benefícios da simplicidade quando se trata de trabalhar com requisitos de dados simples; no entanto, torna-se mais complicado em escala ao trabalhar com dados hierárquicos mais complexos.

Suporte off-line


Ambos os produtos oferecem suporte offline, armazenando dados em cache ativamente em filas quando há conectividade de rede latente ou inexistente, sincronizando as alterações locais com o back-end quando possível. O Firestore oferece suporte à sincronização offline para aplicativos da Web, além de aplicativos para dispositivos móveis, enquanto o Realtime Database permite apenas a sincronização para dispositivos móveis.

Consultas e transações 


O Realtime Database oferece suporte apenas a recursos limitados de classificação e filtragem — você só pode classificar ou filtrar em um nível de propriedade, mas não em ambos, em uma única consulta. As consultas também são profundas, o que significa que elas retornam uma grande subárvore de resultados. O produto suporta apenas operações simples de gravação e transação que exigem um retorno de chamada de conclusão.

O Firestore, por outro lado, apresenta consultas de índice com classificação e filtragem compostas, permitindo combinar ações para criar filtros e classificação em cadeia. Você também pode executar consultas superficiais retornando subcoleções no lugar de toda a coleção que você obteria com o Realtime Database. As transações são de natureza atômica, quer você envie uma operação em lote ou uma única, com transações se repetindo automaticamente até serem concluídas. Além disso, o Realtime Database suporta apenas transações de gravação individuais, enquanto o Firestore oferece operações em lote atomicamente.

Desempenho e escalabilidade


O Realtime Database, como seria de esperar, é bastante robusto e possui baixa latência. No entanto, os bancos de dados são restritos a regiões únicas, sujeitos à disponibilidade zonal. O Firestore, por outro lado, armazena dados horizontalmente em várias zonas e regiões para garantir disponibilidade, escalabilidade e confiabilidade globais reais. De fato, o Google prometeu que o Firestore será mais confiável que o Realtime Database.

Outra deficiência do Realtime Database é a limitação de 100.000 usuários simultâneos (100.000 conexões simultâneas e 1.000 gravações/segundo em um único banco de dados) após o qual você teria que fragmentar seu banco de dados (dividir seu banco de dados em vários bancos de dados) para oferecer suporte a mais usuários . O Firestore é dimensionado automaticamente em várias instâncias sem que você precise intervir.

Projetado desde o início com escalabilidade em mente, o Firestore tem uma nova arquitetura esquemática que replica dados em várias regiões, cuida da autenticação e lida com outros assuntos relacionados à segurança, tudo em seu SDK do lado do cliente. Seu novo modelo de dados é mais intuitivo que o do Firebase, assemelhando-se mais a outras soluções de banco de dados NoSQL comparáveis, como o MongoDB, ao mesmo tempo em que fornece um mecanismo de consulta mais robusto.

Segurança 


Por fim, o Realtime Database, como você sabe em nossos tutoriais anteriores, gerencia a segurança por meio de regras em cascata com acionadores de validação separados. Isso funciona com as regras do banco de dados do Firebase, validando seus dados separadamente. O Firestore, por outro lado, fornece um modelo de segurança mais simples e poderoso, aproveitando as regras de segurança do Cloud Firestore e o Identity and Access Management (IAM), com exceção da validação de dados automaticamente.
  • Desenvolvimento para dispositivos móveisRegras de segurança do FirebaseChike Mgbemena

O modelo de dados do Firestore


Firestore é um banco de dados baseado em documentos NoSQL, que consiste em coleções de documentos, cada um contendo dados. Como é um banco de dados NoSQL, você não obterá tabelas, linhas e outros elementos que encontraria em um banco de dados relacional, mas sim conjuntos de pares chave/valor que encontraria em documentos.

Você cria documentos e coleções implicitamente atribuindo dados a um documento e, se o documento ou coleção não existir, ele será criado automaticamente para você, pois a coleção sempre precisa ser o nó raiz (primeiro). Aqui está um esquema simples de exemplo de Tarefas do projeto em que você estará trabalhando em breve, consistindo na coleção de Tarefas, bem como vários documentos contendo dois campos, o nome (string) e um sinalizador para saber se a tarefa foi concluída (booleano) .

Vamos decompor cada um dos elementos para que você possa entendê-los melhor.

Coleções


Sinônimo de tabelas de banco de dados no mundo SQL, as coleções contêm um ou mais documentos. As coleções precisam ser os elementos raiz do esquema e podem conter apenas documentos, não outras coleções. No entanto, você pode consultar um documento que, por sua vez, se refere a coleções (subcoleções).

No diagrama acima, uma tarefa consiste em dois campos primitivos (nome e feito), bem como uma subcoleção (subtarefa) que consiste em dois campos primitivos próprios.

Documentos


Os documentos consistem em pares de chave/valor, com os valores tendo um dos seguintes tipos:
  • campos primitivos (como strings, números, booleanos)
  • objetos aninhados complexos (listas ou matrizes de primitivos)
  • subcoleções

Objetos aninhados também são chamados de mapas e podem ser representados da seguinte forma, dentro do documento. Veja a seguir um exemplo de um objeto aninhado e uma matriz, respectivamente:
ID: 2422892 //primitive
name: “Remember to buy milk” 
detail: //nested object
    notes: "This is a task to buy milk from the store"
	created: 2017-04-09
	due: 2017-04-10
done: false
notify: ["2F22-89R2", "L092-G623", "H00V-T4S1"]
...

Para obter mais informações sobre os tipos de dados compatíveis, consulte a documentação Tipos de dados do Google. Em seguida, você configurará um projeto para funcionar com o Cloud Firestore.

Configurando o projeto


Se você já trabalhou com o Firebase antes, muito disso deve ser familiar para você. Caso contrário, você precisará criar uma conta no Firebase e seguir as instruções na seção "Configurar o projeto" do nosso tutorial anterior, Introdução ao Firebase Authentication para iOS .

Para acompanhar este tutorial, clone o repositório do projeto do tutorial. Em seguida, inclua a biblioteca do Firestore por adicionando o seguinte ao seu Podfile :
pod 'Firebase/Core' 
pod 'Firebase/Firestore'

Digite o seguinte em seu terminal para construir sua biblioteca:
pod install

Em seguida, mude para o Xcode e abra o .xcworkspace Arquivo. Navegue até o AppDelegate.swift  e digite o seguinte em application:didFinishLaunchingWithOptions: método:
FirebaseApp.configure()

No seu navegador, acesse o Firebase console e selecione oBanco de dados guia à esquerda.

Certifique-se de selecionar a opção para Iniciar no modo de teste para que você não tenha problemas de segurança enquanto fazemos experimentos e observe o aviso de segurança ao mover seu aplicativo para produção. Agora você está pronto para criar uma coleção e alguns documentos de amostra.

Adicionar uma coleção e um documento de amostra


Para começar, crie uma coleção inicial, Tasks , selecionando o botão Adicionar coleção botão e nomear a coleção, conforme ilustrado abaixo:

Para o primeiro documento, você deixará o ID do documento em branco, o que gerará automaticamente um ID para você. O documento consistirá simplesmente em dois campos: namedone .

Salve o documento e você poderá confirmar a coleta e o documento junto com o ID gerado automaticamente:

Com o banco de dados configurado com um documento de amostra na nuvem, você está pronto para começar a implementar o SDK do Firestore no Xcode.

Criando e trabalhando com referências de banco de dados


Abra o MasterViewController.swift arquivo no Xcode e adicione as seguintes linhas para importar a biblioteca:
import Firebase

class MasterViewController: UITableViewController {
    @IBOutlet weak var addButton: UIBarButtonItem!
    
    private var documents: [DocumentSnapshot] = []
    public var tasks: [Task] = []
    private var listener : ListenerRegistration!
   ...

Aqui você está simplesmente criando uma variável de escuta que permitirá acionar uma conexão com o banco de dados em tempo real quando houver uma alteração. Você também está criando um DocumentSnapshot referência que conterá o instantâneo de dados temporário.

Antes de continuar com o controlador de visualização, crie outro arquivo rápido, Task.swift , que representará seu modelo de dados:
import Foundation

struct Task{
    var name:String
    var done: Bool
    var id: String
    
    var dictionary: [String: Any] {
        return [
            "name": name,
            "done": done
        ]
    }
}

extension Task{
    init?(dictionary: [String : Any], id: String) {
        guard   let name = dictionary["name"] as? String,
            let done = dictionary["done"] as? Bool
            else { return nil }
        
        self.init(name: name, done: done, id: id)
    }
}

O trecho de código acima inclui uma propriedade de conveniência (dicionário) e um método (init) que facilitará o preenchimento do objeto de modelo. Volte para o controlador de exibição e declare uma variável setter global que restringirá a consulta base às 50 principais entradas na lista de tarefas. Você também removerá o ouvinte assim que definir a variável de consulta, conforme indicado no didSet propriedade abaixo:
fileprivate func baseQuery() -> Query {
        return Firestore.firestore().collection("Tasks").limit(to: 50)
    }
    
    fileprivate var query: Query? {
        didSet {
            if let listener = listener {
                listener.remove()
            }
        }
    }

override func viewDidLoad() {
        super.viewDidLoad()
        self.query = baseQuery()
    }

 override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.listener.remove()
    }

Ler dados em tempo real do Cloud Firestore


Com a referência do documento no lugar, em viewWillAppear(_animated: Bool) , associe o ouvinte que você criou anteriormente aos resultados do instantâneo da consulta e recupere uma lista de documentos. Isso é feito chamando o método do Firestore query?.addSnapshotListener :
self.listener =  query?.addSnapshotListener { (documents, error) in
            guard let snapshot = documents else {
                print("Error fetching documents results: \(error!)")
                return
            }
            
            let results = snapshot.documents.map { (document) -> Task in
                if let task = Task(dictionary: document.data(), id: document.documentID) {
                    return task
                } else {
                    fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
                }
            }
            
            self.tasks = results
            self.documents = snapshot.documents
            self.tableView.reloadData()
            
        }

O encerramento acima atribui o arquivo snapshot.documents mapeando a matriz iterativamente e agrupando-a em uma nova Task objeto de instância de modelo para cada item de dados no instantâneo. Então, com apenas algumas linhas, você leu com sucesso todas as tarefas da nuvem e as atribuiu às tasks globais   variedade.

Para exibir os resultados, preencha o seguinte TableView métodos de delegação:
override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        
        let item = tasks[indexPath.row]
        
        cell.textLabel!.text = item.name
        cell.textLabel!.textColor = item.done == false ? UIColor.black : UIColor.lightGray
        
        return cell
    }

Nesta fase, construa e execute o projeto e no Simulador você poderá observar os dados aparecendo em tempo real. Adicione dados por meio do console do Firebase e você deverá vê-los aparecer instantaneamente no simulador de aplicativos.

Criação, atualização e exclusão de dados


Depois de ler com sucesso o conteúdo do back-end, você criará, atualizará e excluirá dados. O próximo exemplo ilustrará como atualizar dados, usando um exemplo artificial em que o aplicativo só permitirá que você marque um item como concluído tocando na célula. Observe o arquivo collection.document( item.id ).updateData(["done": !item.done]) closure, que simplesmente faz referência a um ID de documento específico, atualizando cada um dos campos do dicionário:
override func tableView(_ tableView: UITableView,
                            didSelectRowAt indexPath: IndexPath) {

        let item = tasks[indexPath.row]
        let collection = Firestore.firestore().collection("Tasks")

        collection.document(item.id).updateData([
            "done": !item.done,
            ]) { err in
                if let err = err {
                    print("Error updating document: \(err)")
                } else {
                    print("Document successfully updated")
                }
        }

        tableView.reloadRows(at: [indexPath], with: .automatic)
        
    }

Para excluir um item, chame o arquivo document( item.id ).delete() método:

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

        if (editingStyle == .delete){
            let item = tasks[indexPath.row]
            _ = Firestore.firestore().collection("Tasks").document(item.id).delete()
        }

    }

A criação de uma nova tarefa envolverá adicionar um novo botão em seu Storyboard e conectar sua IBAction para o controlador de visualização, criando um addTask(_ sender:) método. Quando um usuário pressiona o botão, ele abre uma folha de alerta onde o usuário pode adicionar um novo nome de tarefa:
collection("Tasks").addDocument
    (data: ["name": textFieldReminder.text ?? 
        "empty task", "done": false])


Complete a parte final do aplicativo digitando o seguinte:
@IBAction func addTask(_ sender: Any) {
        
        let alertVC : UIAlertController = UIAlertController(title: "New Task", message: "What do you want to remember?", preferredStyle: .alert)
        
        alertVC.addTextField { (UITextField) in
            
        }
        
        let cancelAction = UIAlertAction.init(title: "Cancel", style: .destructive, handler: nil)
        
        alertVC.addAction(cancelAction)
        
        //Alert action closure
        let addAction = UIAlertAction.init(title: "Add", style: .default) { (UIAlertAction) -> Void in
            
            let textFieldReminder = (alertVC.textFields?.first)! as UITextField
            
            let db = Firestore.firestore()
            var docRef: DocumentReference? = nil
            docRef = db.collection("Tasks").addDocument(data: [
                "name": textFieldReminder.text ?? "empty task",
                "done": false
            ]) { err in
                if let err = err {
                    print("Error adding document: \(err)")
                } else {
                    print("Document added with ID: \(docRef!.documentID)")
                }
            }
            
        }
    
        alertVC.addAction(addAction)
        present(alertVC, animated: true, completion: nil)
        
    }

Crie e execute o aplicativo mais uma vez e, quando o simulador aparecer, tente adicionar algumas tarefas, marcar algumas como concluídas e, finalmente, testar a função de exclusão removendo algumas tarefas. Você pode confirmar que os dados armazenados foram atualizados em tempo real alternando para o console do banco de dados do Firebase e observando a coleção e os documentos.

Filtragem e consultas compostas


Até agora, você trabalhou apenas com uma consulta simples, sem nenhum recurso de filtragem específico. Para criar consultas um pouco mais robustas, você pode filtrar por valores específicos usando um whereField cláusula:
docRef.whereField(“name”, isEqualTo: searchString)

Você pode ordenar e limitar seus dados de consulta usando o comando order(by: ) e limit(to: ) métodos da seguinte forma:
docRef.order(by: "name").limit(5)

No aplicativo FirebaseDo, você já usou limit com a consulta básica. No snippet acima, você também fez uso de outro recurso, consultas compostas, em que a ordem e o limite são encadeados. Você pode encadear quantas consultas quiser, como no exemplo a seguir:
docRef
    .whereField(“name”, isEqualTo: searchString)
	.whereField(“done”, isEqualTo: false)
	.order(by: "name")
	.limit(5)