Android

17 jan, 2018

App Android com Firebase – Realtime Database

Publicidade

Este artigo é uma continuação da série “App Android com Firebase”, onde ensino como utilizar todos os serviços oferecidos pela plataforma de Backend-as-a-service do Google. Hoje, vou falar do serviço mais famoso deles: o realtime database, que permitiu que muitos desenvolvedores de apps que não sabem criar APIs para fazer conexão com banco de dados externo, pudessem colocar seus apps em produção rapidamente.

Não apenas isso, mas como o próprio nome sugere, a realtime database do Firebase permite comunicação em tempo real com o banco de dados, incluindo uma arquitetura no melhor estilo dos padrões Observer e Pub-Sub. Além disso, permite salvamento offline com sincronização automática quando a conexão é restabelecida.

Usarei como exemplo neste artigo, o app ForcaVendas que iniciamos no primeiro artigo desta série. No entanto, não se preocupe, ele apenas possui uma tela de login e uma MainActivity com um olá mundo. Sendo assim, você consegue acompanhar sem ter lido os anteriores, desde que já saiba como programar para Android, é claro. Também existe a possibilidade de baixar os fontes do app através do formulário existente no final de cada artigo desta série.

1. Configurando o Realtime Database

Primeiramente, para configurar o database do Firebase para o seu app, vá no menu Tools > Firebase do Android Studio, e clique na opção “Save and retrieve data” da seção Realtime Database.

Configurando o Firebase

Na seção seguinte que vai se abrir, temos os passos de configuração propriamente ditos. O primeiro passo é bem simples e exige que você se conecte ao Firebase primeiro.

Realtime Database

O segundo passo adiciona as dependências para se trabalhar com Realtime Database no seu app e pedirá confirmação para instalar dependências e alterar alguns arquivos de configuração.

Feito isso, estamos prontos para usar nosso banco realtime!

2. Entendendo o Realtime Database

Como explicado no site oficial do Firebase Realtime Database:

O Firebase Realtime Database é um banco de dados hospedado na nuvem. Os dados são armazenados como JSON e sincronizados em tempo real com todos os clientes conectados.

Ou seja, você cria o seu banco na nuvem do Firebase e conecta seu app nele para enviar e receber objetos JSON em tempo real, simples assim. As mudanças no banco são refletidas automaticamente para todos os dispositivos – e aplicações, pois você pode usar na web também – conectados, em tempo real (daí o nome). Isso lembra os documentos do MongoDB. O site oficial ainda diz que:

Todos os dados do Firebase Realtime Database são armazenados como objetos JSON. Pense no banco de dados como uma árvore JSON hospedada na nuvem. Ao contrário de um banco de dados SQL, não há tabelas nem registros. Quando você adiciona dados à árvore JSON, eles se tornam um node na estrutura JSON com uma chave associada.

Um recurso muito importante do Realtime Database, é que combinando com o suporte de autenticação do Firebase, você pode definir quem tem acesso a quais dados, garantindo a autorização dos mesmos. Existe uma opção de deixar o seu banco público, mas na maioria dos casos isso não é recomendado.

Como o Realtime Database é associado ao seu projeto Firebase, por padrão ele só permite o acesso aos dados por usuários autenticados, como mostra a imagem abaixo do Firebase Console.

Caso não tenha lido o artigo de autenticação em Firebase, você pode opcionalmente deixar os dados públicos na mesma tela acima. De qualquer forma, recomendo dar uma olhada no respectivo artigo para entender o básico da plataforma Firebase, pois é lá onde explico isso.

Outro recurso interessantíssimo do Realtime Database é o suporte offline. Se o seu app ficar sem Internet, os dados serão salvos localmente e depois sincronizados quando a conexão voltar.

Assim como no MongoDB, a modelagem de um banco no Realtime Database deve ser feita com cuidado, sempre tendo em mente como desejará consultar estes dados depois. Algumas dicas incluem:

  • Não aninhe muitos níveis dentro dos seus objetos JSON. Apesar do Firebase suportar até 32 níveis, isso não é uma boa prática por tornar consultas muito pesadas e escritas muito arriscadas.
  • Evite a normalização excessiva (espalhando demais os dados) e foque na consulta rápida, mesmo que isso exija duplicação de dados. Em bancos NoSQL, tamanho não é problema, desde que o dado esteja fácil de ler e baixar.

Vale salientar que dificilmente você terá custos enquanto estiver estudando como usar o Realtime Database, mas que dificilmente não terá considerado que para seu app ir para produção, você irá querer recursos mais avançados como backup do seu banco, por exemplo, disponível apenas nos planos superiores.

3.  Criando a tela de Cadastro

Mesmo que não tenha feito os outros artigos de Firebase aqui do portal, não tem problema. Você só precisa ter uma aplicação com o template padrão do Android Studio, aquele com a MainActivity contendo um “Hello World” e um floating button no canto inferior direito.

Nos artigos anteriores comentei que iria fazer um app de Força de Vendas para autônomos. A ideia é apenas registrar os pedidos, compras e dívidas, de uma maneira fácil de buscar depois, ajudando na organização do vendedor, que geralmente usa caderninhos para isso.

Assim, essa MainActivity (tela inicial) futuramente será a listagem de registros principal do app. Como não temos dados para serem listados ainda, vamos apenas programar o floating button dessa tela para nos levar a tela de cadastro.

Essa alteração é bem simples, troque o ícone do floating button para algo que faça mais sentido do que o envelope tradicional usando o XML abaixo no activity_main.xml (dentro da pasta res/layout):

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_input_add" />

Note que alterei somente a propriedade srcCompat que determina o ícone, onde estou usando o ícone de sistema do botão de “+”, que representa bem uma adição.

Agora vamos criar a próxima Activity, de cadastro, do tipo Empty Activity com o nome de AddActivity, aproveitando que o Android Studio criará para nós o arquivo de layout activity_add.xml automaticamente. Mas o que irá ter nesta tela?

Pensando em um fluxo de uso bem simples, os registros deverão conter uma descrição, um valor, uma data e um tipo. O tipo pode ser Venda Paga, Venda Não Paga (fiado, bem comum entre vendedores autônomos) e Venda Futura (apenas tirou um pedido). Obviamente esta é apenas uma proposta, você pode criar algo mais elaborado se quiser.

Com isso em mente, edite seu activity_add.xml para que pareça com isso:

Tela de Cadastro

Caso você não faça ideia de como criar este layout, recomendo ler o artigo sobre Constraint Layout primeiro ou verificar as diversas lições presentes em meu livro. Se estiver na correria, apenas copie e cole o código XML abaixo no seu activity_add.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="br.com.luiztools.forcavendas.AddActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Descrição"
        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/txtDescricao"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:inputType="textMultiLine"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large"
        android:text="Valor: Rquot;
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txtDescricao" />

    <EditText
        android:id="@+id/txtValor"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:inputType="numberDecimal"
        android:text="0,00"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/txtDescricao" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Data: "
        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Large"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <EditText
        android:id="@+id/txtData"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:inputType="date"
        android:text="07/01/2018"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView3"
        app:layout_constraintTop_toBottomOf="@+id/txtValor" />

    <Spinner
        android:id="@+id/spnTipo"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:entries="@array/tipos"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txtData" />

    <Button
        android:id="@+id/btnSalvar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Salvar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/spnTipo" />
</android.support.constraint.ConstraintLayout>

Note que, para o Spinner exibir corretamente os tipos, eu tive de criar outro arquivo XML, um de valores (tipos.xml) na pasta res/values. Neste arquivo XML de strings, o qual eu reproduzo abaixo, apenas listo os textos dos valores possíveis, considerando aqui que eles são fixos (caso contrário eu deveria carregar do banco).

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="tipos">
        <item>Venda Paga</item>
        <item>Venda Não-Paga</item>
        <item>Venda Futura</item>
    </string-array>
</resources>

E para referenciar este XML no Spinner (o componente visual de seleção de tipo), você usa a propriedade entries, como abaixo (esse código já está contemplado no XML completo da página que mostrei antes):

    <Spinner
        android:id="@+id/spnTipo"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:entries="@array/tipos"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txtData" />

Agora, para conseguirmos chegar até esta página e ver se a tela ficou ok no simulador ou no seu device android físico, devemos programar a ação do floating button existente na MainActivity. Essa ação já vem programada no evento onCreate da Actvity por padrão, para apenas exibir uma mensagem, vamos trocar para realizar a troca de tela.

FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
       Intent intent = new Intent(MainActivity.this, AddActivity.class);
       startActivity(intent);
    }
});

Com isso programado, já temos o suficiente para testar a nossa aplicação no simulador e ver se conseguimos chegar até a tela de cadastro. Se você veio acompanhando este artigo deste o início, terá de se autenticar como um usuário válido primeiro.

Após você testar e se certificar que este básico está funcionando, vamos fazer mais um ajuste pequeno, porém útil para facilitar a digitação.

Data Atual

O campo data deve vir com a data atual por padrão. Para fazer isso, no onCreate da AddActivity, adicione o seguinte código que pega a data atual e a coloca no campo com id txtData (eu defini esse id para o campo de data, talvez o seu seja diferente):

public class AddActivity extends AppCompatActivity {

    EditText txtData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add);

        txtData = (EditText)findViewById(R.id.txtData);
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
        txtData.setText(sdf.format(new Date()));
    }
}

Quando você abrir a tela de cadastro, encontrará a data atual no respectivo campo. É uma coisa pequena, mas ajuda.

Como este artigo está ficando extenso, deixarei a parte de conexão com o banco para leitura e escrita para o próximo artigo que deve sair em breve. Aguarde!