3
respostas

[Dúvida] Integrar com Firebase Realtime Database

To tentando integrar o sistema a um banco que criei no Firebase Realtime Database.

1) Importei no proprio Firebase Console o arquivo JSON do projetoe ele me criou a estrutura numerada de 0 a 13 da imagem abaixo. 2) Dá pra ver o próximo item da tabela é um id randomico gerado pelo firebase que não consegui gerenciar pela aplicação, creio que o JSON server faça esse papel.

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

No projeto eu configurei minha const API para o endpoit que o firebase fornece para o projeto:

private readonly API = 'https://angular-memoteca-5b5b9-default-rtdb.firebaseio.com/pensamentos.json';

Com essa configuração eu consigo recuperar os dados e apresentar no mural, mas o restante das operações não consegui realizar.

Quando usando o JSON Server localmente parece que ele faz a gerência desses índices, o que não temos quando puxamos do Firebase:

1) A referência para encontrar um objeto agora nao funciona mais no id. No exemplo abaixo, o item de posiçao 0 tem id 1, mas para encontra-lo pelo endpoit do firebase tenho que usar 0. Logo essa mudança reflete pra todas as demais ações que o serviço presta.

Insira aqui a descrição dessa imagem para ajudar na acessibilidadeComo encontrar pelo firebase: https://angular-memoteca-5b5b9-default-rtdb.firebaseio.com/pensamentos/0.json

Se alguém tiver uma luz de como fazer essa mudança do json server pro firebase ficaria agradecido.

3 respostas

Olá Uiratan, tudo bem?

Conversei com a equipe sobre o tópico que você criou e um colega de trabalho que manja muito de Firebase, o Ricarth Lima, analisou sua dúvida e disponibilizou o direcionamento que vou te passar a seguir.

Vamos lá!

  • O Firebase tem dois bancos de dados online NoSQL em tempo real:
    • Cloud Firestore: Mais novo, mais bem estruturado, mais suportes à região, mais bibliotecas de integração etc; e o
    • Realtime Database: O que você usou, que realmente segue a estrutura de um JSON, e provavelmente por essa característica você resolveu usar esse banco.

Realtime Database x JSON

  • Você notou uma peculiaridade: apesar de seguir a estrutura de um JSON, o Realtime Database não é de fato um JSON:

    • No JSON podemos fazer, por exemplo, a atribuição: chave, valor. O que não é possível no Realtime Database que só aceita esses tipos de valores:

      Insira aqui a descrição dessa imagem para ajudar na acessibilidade

    • Portanto, quando ele importa o db.json que contem uma lista, essa lista é convertida para um Map onde cada chave é uma String que “itera” sob a ordem universal de 0,1,2,3…, e o valor é um Map contendo o valor do respectivo índice;

    • Como tentar converter uma lista para um dicionário?

  • Daí, acredito que o maior desafio é adaptar o código para seguir essa lógica: precisamos garantir a consistência entre o campo id da classe Pensamentos e a chave que representa esse pensamento no Realtime Database.

Mão na massa

A melhor solução (ponderando complexidade e assertividade) é reestruturar a aplicação para não mais usar o campo “id” como fonte da informação do id, e sim a chave no Realtime Database.

A aplicação teria que gerenciar isso, pois o Realtime Database não tem essa inteligência.

Lendo, Editando ou Removendo por Id

Ou seja, ao ler, editar ou remover um Pensamento, você vai ter que olhar qual o id que está registrado localmente no atributo e fazer essa chamada para o Realtime Database.

Algo como:

  • getAllPensamentos: http.get('$url/pensamentos.json')
  • getPensamentoById: http.get('$url/pensamento/id.json')
  • editPensamento: http.put('$url/pensamento/id.json')
  • removerPensamento: http.delete('$url/pensamento/id.json')

Mas acredito que isso você já fez, o segredo está na hora de adicionar.

Adicionando com Id

É necessário que você faça as adições seguindo a lógica da lista e crie (primeiro localmente) um novo Pensamento onde o id seja a posição na lista. Tipo assim:

  • addPensamento:
    • http.put('$url/pensamento/id.json')
    • Aqui vai ter que usar um PUT ao invés de um POST mesmo para adicionar, pois, por padrão, o Realtime Database adicionaria com um uuid aleatório (como você mesmo notou), é uma lógica similar a editar um map onde Adicionar e Editar é a mesma sintaxe.

Não adianta, se você usa o POST para adicionar em qualquer nível no Realtime Database ele cria um id aleatório, que para nosso caso aqui quebra toda a lógica do sistema. O PUT é a solução, já que caso você tente “editar” algo que não existe ele simplesmente cria com o ID que você passou.

De resto, é implementar no código a consistência entre o campo id da classe Pensamento, e a chave Id do Realtime Database, pois essa lógica de ids (que possuem ordem entre si) via numeração agora vai ter que ser implementada no código.

Sobre o problema na importação, você pode simplesmente não importar o db.json e começar do zero o banco no Realtime Database. Ou pode adicionar (por escrito no bloco de notas mesmo) um primeiro objeto com id 0 antes de importar o db.json.

Segue a documentação do Firebase utilizada como referência

Espero que te ajude, abraços, bons estudos!

Professora, muito obrigado, entendi bem melhor e to conseguindo gerar uma solução que não altere tanto o projeto.

Queria só mais uma ajuda com os observables: A solução que eu vi seria usar o próprio método listar() e adicionar +1 ao tamanho do resultado, mas a variável fora do .subscribe não recebe o valor adicionado, sempre vem undefined.

criar(pensamento: Pensamento): Observable<Pensamento> {
   let proxId: number = 0;
    this.listar().subscribe((listaPensamentos) => {
      proxId = listaPensamentos.length+1;
    });

    let url = `${this.API}/${this.proxId}.json`;
    return this.http.put<Pensamento>(url, pensamento);
  }

Teria que sempre usar uma variável a nível de classe para pegar estes valores?

Para não alterar tanto o projeto, fiz o seguinte:

  1. Criei a interface PersistenceService para servir de contrato para as implementações que cada banco deve prover para a classe PensamentoService.
import { Pensamento } from './pensamento';
import { Observable } from 'rxjs'

export interface PersistenceService {

  readonly API: string;

  listar(): Observable<Pensamento[]>;
  criar(pensamento: Pensamento): Observable<Pensamento>;
  excluir(id: number): Observable<Pensamento>;
  buscarPorId(id: number): Observable<Pensamento>;
  editar(pensamento: Pensamento): Observable<Pensamento>;

}
  1. Criei duas implementações de PersistenceService: JsonService e FirebaseService.

2.1. Passei para JsonService o código que antes estava em PensamentoService

import { PersistenceService } from './persistence.service';
import { Pensamento } from './pensamento';
import { Observable } from 'rxjs'
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class JsonService implements PersistenceService {

  readonly API = 'http://localhost:3000/pensamentos';

  constructor(
      private http: HttpClient
    ) { }

  listar(): Observable<Pensamento[]> {
    return this.http.get<Pensamento[]>(this.API);
  }

  criar(pensamento: Pensamento): Observable<Pensamento> {
    return this.http.post<Pensamento>(this.API, pensamento);
  }


  excluir(id: number): Observable<Pensamento> {
    const url = `${this.API}/${id}`;
    return this.http.delete<Pensamento>(url);
  }

  buscarPorId(id: number): Observable<Pensamento> {
    const url = `${this.API}/${id}`;
    return this.http.get<Pensamento>(url);
  }

  editar(pensamento: Pensamento): Observable<Pensamento> {
    const url = `${this.API}/${pensamento.id}`;
    return this.http.put<Pensamento>(url, pensamento);
  }

}

2.2. FirebaseService é aqui no método criar que ainda to empacado.

import { PersistenceService } from './persistence.service';
import { Pensamento } from './pensamento';
import { Observable } from 'rxjs'
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class FirebaseService implements PersistenceService {

  /*
  getAllPensamentos: http.get('$url/pensamentos.json')
  addPensamento: http.put('$url/pensamentos/id.json')
  getPensamentoById: http.get('$url/pensamentos/id.json')
  editPensamento: http.put('$url/pensamentos/id.json')
  removerPensamento: http.delete('$url/pensamentos/id.json')
  */
  readonly API = 'https://angular-memoteca-5b5b9-default-rtdb.firebaseio.com/pensamentos';

  proxId: number = 0;

  constructor(
    private http: HttpClient
  ) { }

  listar(): Observable<Pensamento[]> {
    //console.log(this.gerarId());
    const url = `${this.API}.json`;
    return this.http.get<Pensamento[]>(url);
  }

  buscarPorId(id: number): Observable<Pensamento> {
    const url = `${this.API}/${id}.json`;

    console.log(`Buscando pelo id: : ${url}`);

    return this.http.get<Pensamento>(url);
  }

  criar(pensamento: Pensamento): Observable<Pensamento> {
    this.listar().subscribe((listaPensamentos) => {
      this.proxId = listaPensamentos.length+1;
    });

    let url = `${this.API}/${this.proxId}.json`;
    return this.http.put<Pensamento>(url, pensamento);
  }

  excluir(id: number): Observable<Pensamento> {
    const url = `${this.API}/${id}.json`;
    return this.http.delete<Pensamento>(url);
  }


  editar(pensamento: Pensamento): Observable<Pensamento> {
    const url = `${this.API}/${pensamento.id}.json`;
    console.log(url);

    return this.http.put<Pensamento>(url, pensamento);
  }

}
  1. PensamentoService só recebo via construtor a implementação que eu desejo usar, neste caso FirebaseService. Aceito mais sugestões!
import { Pensamento } from './pensamento';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
import { JsonService } from './json.service';
import { FirebaseService } from './firebase.service';
@Injectable({
  providedIn: 'root'
})
export class PensamentoService {
  constructor(
      private http: HttpClient,
      private dbService: FirebaseService
    ) { }
  listar(): Observable<Pensamento[]> {
    return this.dbService.listar();
  }
  criar(pensamento: Pensamento): Observable<Pensamento> {
    return this.dbService.criar(pensamento);
  }
  excluir(id: number): Observable<Pensamento> {
    return this.dbService.excluir(id);
  }
  buscarPorId(id: number): Observable<Pensamento> {
    return this.dbService.buscarPorId(id);
  }
  editar(pensamento: Pensamento): Observable<Pensamento> {
    return this.dbService.editar(pensamento);
  }
}

https://github.com/uiratan/angular-memoteca