sábado, 10 de junho de 2017

CRUD básico com Android e Firebase (parte 4)

Armazenando dados


Lembrando que esta série de posts é fortemente baseada neste post. Inclusive, recebi autorização do próprio autor do post original para realizar esta tradução.

A idéia é desenvolver uma pequena aplicação para armazenar artistas, e músicas (Tracks - “trilhas”) relacionadas com esses artistas. Se fosse utilizado um sistema gerenciador de banco de dados relacional tradicional, teríamos tabelas como apresentadas na figura abaixo:


Na tabela “Artists”, temos um ArtistiId (a chave primária), o nome do artista, e seu gênero musical. Já na tabela “Tracks” (trilhas), temos também um Id (TrackId, chave primária desta tabela), o nome da música (TrackName), uma nota de avaliação (TrackRating) e o Id do artista a quem esta música pertence (ArtistId, chave estrangeira). Mais formalmente:


Como já vimos, o FireBase não estrutura os dados em tabelas relacionadas, mas sim em uma “árvore JSON”. Os dados acima, portanto, ficariam estruturados da seguinte forma:


Não será objetivo desta série mostrar como converter um esquema relacional em um esquema noSQL (para referências neste tópico, ver no primeiro post desta série). Isto provavelmente será objeto de outra série de posts no futuro. Focaremos aqui, portanto, apenas na implementação.

Vamos começar montando nossa interface para a inserção de artistas no banco de dados. No rascunho abaixo, podemos ver uma caixa de texto para digitação do nome, um “spinner” para selecionar um entre alguns gêneros músicais pré-configurados, e um botão para efetivamente realizar a inserção no banco de dados.


Não é nosso objetivo aqui detalhar o processo de criação da interface (para um maior detalhamento deste processo, acesse as vídeo-aulas constantes do post original, onde o autor mostra passo-a-passo a contrução da interface). Portanto, já disponibilizarei os códigos prontos.

Vamos criar um array de itens para servir de conteúdo para o “spinner” (gêneros músicais).
Acesse o arquivo em res/values/strings.xml, e insira o código abaixo:


    <array name="genres">
        <item>Rock</item>
        <item>Pop</item>
        <item>New Age</item>
        <item>Blues</item>
    </array>

Agora troque o conteúdo do arquivo “activity_main.xml” pelo código abaixo:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="br.com.laststarfighter.germusica.MainActivity">

    <EditText
        android:id="@+id/editTextName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="19dp"
        android:ems="10"
        android:hint="Enter name "
        android:inputType="textPersonName" />

    <Spinner
        android:id="@+id/spinnerGenres"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:entries="@array/genres"
        android:layout_below="@+id/editTextName"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        android:id="@+id/buttonAddArtist"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/spinnerGenres"
        android:layout_marginTop="17dp"
        android:text="ADD ARTIST" />

</RelativeLayout>

Veja abaixo uma repetição da imagem da nossa interface, agora com os nomes das views, para facilitar a referência posteriormente no código:


Segue abaixo o código completo da classe MainActivity (sem os imports e sem o package). Optei por tentar explicar algumas coisas com comentários, no próprio código:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
public class MainActivity extends AppCompatActivity {

    // referências para as views na interface
    EditText editTextName;
    Button buttonAddArtist;
    Spinner spinnerGenres;

    // referência para o banco de dados no firebase.
    // Traduzido da documentação:
    // "uma referência para o firebase representa um local particular
    // no seu banco de dados e pode ser usado para leitura e escrita
    // de dados naquele local.
    // Esta classe é o ponto de partida para todas as operações
    // de banco de dados."
    DatabaseReference databaseArtists;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // classe FirebaseDatabase: ponto de entrada para acessar o BD Firebase
        // getInstance() : referência da instância default de FirebaseDatabase
        // getReference() : retorna uma DatabaseReference no caminho especificado
        // Lembrando... JSON armazena os dados como pares chave/valor.
        // getReference("Artists") retorna uma referência para um par cuja
        // "chave" vai ser "Artists". O valor vai ser um conjunto de objetos
        // que vão ser os "registros" da tabela Artists
        databaseArtists = FirebaseDatabase.getInstance().getReference("Artists");

        // ligando as referências com as views na interface
        editTextName = (EditText) findViewById(R.id.editTextName);
        buttonAddArtist = (Button) findViewById(R.id.buttonAddArtist);
        spinnerGenres = (Spinner) findViewById(R.id.spinnerGenres);

        // definindo o código que vai responder ao evento de clique do botão
        buttonAddArtist.setOnClickListener( new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                // chama o método addArtist(), definido mais abaixo
                addArtist();
            }
        });

    }


    // método para adicionar um artista no banco de dados

    private void addArtist() {

        // pega o nome do artista da caixa de texto:
        // getText() - retorna um "Editable"
        // toString() - converte para String
        // trim() - retira os espaços em branco antes e depois
        String name = editTextName.getText().toString().trim();

        // pega o gênero musical selecionado no spinner:
        // getSelectedItem() - retorna o iten selecionado (object)
        // toString() - converte para string
        String genre = spinnerGenres.getSelectedItem().toString();

        // testando se o usuário digitou um nome realmente,
        // isto é, se a variável "name" está vazia
        if( !TextUtils.isEmpty(name) ) {

            // nao está vazia; podemos gravar no banco

            // lembrando, databaseArtists está apontando
            // para um par chave/valor cuja chave é "Artists"
            // (ver linha 15).
            // O método push() cria um novo par chave/valor
            // no lugar apontado por databaseArtists, isto é,
            // dentro de "Artists". A chave, desse par recém criado,
            // será uma string única (aleatória).
            // Essa string será o campo id do nosso artista.
            // E o valor? Colocaremos depois.
            // O método getKey() retorna essa string criada,
            // para podermos relacionar com o resto dos dados
            // do artista (armazenando em id).
            String id = databaseArtists.push().getKey();

            // aqui simplesmente criamos o objeto Artist,
            // com os dados coletados da interface gráfica
            Artist artist = new Artist(id, name, genre);

            // Lembrando: na linha 83 criamos um "filho" de
            // Artists. Como acessar esse filho? Através de sua
            // chave (lembre sempre, par chave/valor).
            // Então o método child(id) retorna uma referência para
            // este par. Aí, finalmente, o método setValue define
            // o "valor", relativo ao id, com os dados do objeto artist.
            databaseArtists.child(id).setValue(artist);

            // toast avisando que o artista foi adicionado
            Toast.makeText(this, "Artista adicionado", Toast.LENGTH_LONG).show();
        }
        else {
            // se estiver vazia, avisa o usuário com um "toast"
            Toast.makeText(this, "Você deve digitar um nome", Toast.LENGTH_LONG).show();
        }
    }
}

Complicado? Um pouco... vamos tentar ver um passo a passo:

1) O banco no firebase está vazio.

Tem apenas um par chave/valor.... a chave é “gerenciador-de-musicas-c56d3” e o valor relacionado é “null”. Esta chave é a raiz de toda árvore JSON.
(só lembrando... a imagem abaixo é do "console", no site do Firebase, como vimos num post anterior)


2) Na linha 15, dizemos para nosso “databaseArtists” apontar para “Artists”.
Isto significa que se gravarmos alguma coisa, será gravado dentro (ou melhor dizendo, “debaixo”, de “Artists” (chave Artists).

3) Preenchemos os dados na interface, e clicamos em “ADD ARTIST”:


4) Na linha 83, o método push cria um novo par chave/valor, onde “chave” é uma string única (neste exemplo, "-KmEUlabNwi6zLJMWw7O"). Como estamos apontando para “Artists”, esse novo par é criado “debaixo” (ou dentro) de “Artists”.

O método getKey() “pega” essa string criada, para armazenarmos na variável id.

5) Na linha 87, criamos um objeto (java) da classe “Artist”, com o id gerado acima, e nome e gênero musical coletados na interface gráfica.

6) Na linha 95, enfim, definimos o valor (lembra que é um par chave/valor ?) para o filho (child) criado anteriormente, onde o nome está armazenado em id, isto é, child(id).
Para definir o valor, usamos o método setValue, passando o objeto com os dados (artist).

Se olharmos no firebase, teremos...


7) Vamos adicionar outro artista:


8) Como já falamos, vai ser inserido “dentro” da chave "Artists", que passará a ter 2 objetos no seu interior:


Se quiser ver numa cara mais “JSON”, exporte o arquivo, e abra no seu editor de texto-puro preferido...


A gente te ajuda a enxergar as coisas... ;-)


Temos um par chave/valor (em verde), cuja chave é “Artists”, e o valor é formado por 2 pares chave/valor (marcados em vermelho).

Pra finalizar, algumas considerações sobre o método setValue (que define o “valor”, do par chave/valor, linha 95). Na documentação:

Define o valor em um local (no nosso exemplo, a referência retornada por child() ).
Passar null vai apagar os dados nesse local.
Os tipos nativos aceitados por este método correspondem aos tipos de dados JSON:

  • Boolean
  • Long
  • Double
  • Map (String,Object)  -  isto é, aqui, outro par chave/valor
  • List

Adicionalmente, você pode informar instâncias de suas próprias classes (como fizemos com uma instância de Artist) como valor, desde que essas classes satisfaçam os seguintes requisitos:

  • A classe deve ter um construtor default que não receba argumento nenhum;
  • A classe deve definir getters públicos para os valores.

Note que nossa classe Artist segue esses requisitos.

Segue para parte 5...



Nenhum comentário: