sábado, 17 de junho de 2017

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

Relacionando dados


Lembrando e registrando sempre que esta série de posts é uma tradução/expansão deste excelente post, processo que foi autorizado pelo autor original.

No início do post 4, apresentamos o “modelo de dados” da nossa aplicação, isto é, teríamos uma lista de artistas, e teríamos uma lista de “tracks” (trilhas, músicas) RELACIONADA com cada artista. Veremos então como “conectar” (melhor dizer, relacionar) duas entidades no banco de dados do Firebase (Artists com Tracks).

Criaremos uma nova activity, para poder inserir e exibir as “Tracks” de um determinado Artista.

Esta tela será muito parecida com a que já fizemos para os artistas, com pequenas alterações:

  • um TextView para mostrar o nome do artista ao qual estas músicas se relacionam;
  • um EditText para digitar o nome da música a ser inserida;
  • um SeekBar, para dar uma nota (de 0 a 5) a esta música;
  • um botão para efetivamente adicionar a música no banco de dados;
  • um ListView, para mostrar as diversas músicas deste artista.

Veja abaixo como fica a interface desta activity, com os respectivos nomes (id's) das views:


Para criar esta nova activity:

  • botão direito no pacote
  • new | activity | empty activity
  • Name: AddTrackActivity
  • layout name: activity_add_track


Segue abaixo o código xml do arquivo activity_add_track.xml:


<?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">

    <TextView
        android:id="@+id/textViewArtistName"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
        android:textAlignment="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/editTextTrackName"
        android:layout_below="@+id/textViewArtistName"
        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 track name"
        android:inputType="textPersonName" />

    <SeekBar
        android:id="@+id/seekBarRating"
        android:max="5"
        android:layout_below="@+id/editTextTrackName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

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

    <ListView
        android:id="@+id/listViewTracks"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/buttonAddTrack"
        android:layout_marginTop="13dp" />

</RelativeLayout>

Esta activity será chamada quando clicarmos no nome de um artista, na lista existente na activity principal. Desta forma, precisaremos alterar o código da MainActivity, criando um listener para responder ao evento de toque em um item da lista.

Em resumo, este listener (gerente de eventos) deve:
  • Recuperar o objeto artista (armazenado na lista de artistas) com base na posição do item clicado no ListView (a lista na interface)
  • Criar uma intent, para abrir a nova activity (AddTrackActivity), e inserir neste intent os dados de nome do artista, e o id do artista
  • Enfim, iniciar a nova activity.

Segue abaixo o novo código da MainActivity. Novamente, retirei os comentários relativos a posts anteriores, deixando só os pertencentes ao código novo:


public class MainActivity extends AppCompatActivity {

    EditText editTextName;
    Button buttonAddArtist;
    Spinner spinnerGenres;
    ListView listViewArtists;

    List<Artist> artistList;

    DatabaseReference databaseArtists;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

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

        databaseArtists = FirebaseDatabase.getInstance().getReference("Artists");

        editTextName = (EditText) findViewById(R.id.editTextName);
        buttonAddArtist = (Button) findViewById(R.id.buttonAddArtist);
        spinnerGenres = (Spinner) findViewById(R.id.spinnerGenres);
        listViewArtists = (ListView) findViewById(R.id.listViewArtists);

        artistList = new ArrayList<>();

        buttonAddArtist.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                addArtist();
            }
        });

        // definindo um listener para chamar a activity das tracks, quando
        // for clicado um artista na lista de artistas (listViewArtists)
        listViewArtists.setOnItemClickListener(new AdapterView.OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

                // a lista foi clicada, na posição indicada pelo parâmetro i
                // vamos então pegar o objeto artista correspndente a esta posição
                // na lista de artistas
                Artist artist = artistList.get(i);

                // vamos chamar a nova activity, para trabalhar com as musicas (tracks)
                Intent intent = new Intent(getApplicationContext(), AddTrackActivity.class);

                // guardando o nome e o id do artista na intent, para ser recuperada
                // pela nova activity
                intent.putExtra("ARTIST_ID", artist.getArtistId());
                intent.putExtra("ARTIST_NAME", artist.getArtistName());

                // enfim, chama a activity
                startActivity(intent);
            }
        });
    }

    @Override
    protected void onStart() {

        super.onStart();

        databaseArtists.addValueEventListener( new ValueEventListener() {

            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

                artistList.clear();

                for(DataSnapshot artistSnapshot : dataSnapshot.getChildren()) {

                    Artist artist = artistSnapshot.getValue(Artist.class);
                    artistList.add(artist);
                }

                ArtistListAdapter adapter = new ArtistListAdapter(MainActivity.this, artistList);
                listViewArtists.setAdapter(adapter);
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
            }

        });
    }


    // método para adicionar um artista no banco de dados
    private void addArtist() {

        String name = editTextName.getText().toString().trim();
        String genre = spinnerGenres.getSelectedItem().toString();

        if( !TextUtils.isEmpty(name) ) {

            String id = databaseArtists.push().getKey();
            Artist artist = new Artist(id, name, genre);
            databaseArtists.child(id).setValue(artist);

            Toast.makeText(this, "Artista adicionado", Toast.LENGTH_LONG).show();
        }
        else {

            Toast.makeText(this, "Você deve digitar um nome", Toast.LENGTH_LONG).show();
        }
    }
}

Vamos agora trabalhar na activity das trilhas (AddTrackActivity). Porém, antes, precisamos da classe Track, relativa as trilhas que serão gravadas e lidas do banco de dados:


public class Track {

    private String trackID;
    private String trackName;
    private int trackRating;

    public Track () {

    }

    public Track(String trackID, String trackName, int trackRating) {

        this.trackID = trackID;
        this.trackName = trackName;
        this.trackRating = trackRating;
    }

    public String getTrackID() {

        return trackID;
    }

    public String getTrackName() {

        return trackName;
    }

    public int getTrackRating() {

        return trackRating;
    }

}

Com relação a classe AddTrackActivity, você verá que ela é muito parecida com a classe MainActivity, em relação aos procedimentos de gravação de dados. A única novidade, e que você precisa prestar bastante atenção, é no local (chave) onde vão ser gravados os dados das trilhas. Na classe MainActivity, tinhamos apenas a chave “Artists”, e todos os artistas (indexados por suas próprias chaves, que chamamos de “id”) ficavam “debaixo” desta chave.

No caso das trilhas, elas ficarão também debaixo de uma chave principal (“Tracks”), porém agrupadas PELO ID DO ARTISTA. Isto é feito porque, no Firebase, ao passar uma chave, ele vai retornar todos os objetos referenciados por esta chave. Assim, passando o “id” de um artista, teremos como retorno uma lista de objetos que serão as músicas (tracks) daquele artista.

Lembrando uma imagem já mostrada, num post anterior, teremos essa organização na árvore JSON, relacionada as trilhas:


Note, na figura, que temos 2 artistas, debaixo da chave “tracks”.
O primeiro, “artist_id_1”, possui “debaixo” dele duas trilhas, referenciadas respectivamente, com os id's “track_id_1” e “track_id_2”.
O segundo artista, "artist_id_2" tem apenas uma música "debaixo" dele, com id "track_id_3".

Lembrando que esses "id's" serão strings criadas aleatoriamente, para serem únicas.

Note também, de diferente nesta classe, é a recuperação de informações (o nome do artista e o id do artista) que foram “inseridas” na intent que criou esta nova activity.

Segue o código então, com os devidos comentários (mais simplificados, em especial nos itens que o código é muito parecido ao que já foi visto para a MainActivity):


public class AddTrackActivity extends AppCompatActivity {

    // criando referências para as views na interface
    TextView textViewArtistName;
    EditText editTextTrackName;
    SeekBar seekBarRating;
    Button buttonAddTrack;

    ListView listViewTracks;

    // referência para a chave onde vamos criar as coisas
    // no firebase
    DatabaseReference databaseTracks;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

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

        // ligando as referências com as views na interface...
        textViewArtistName = (TextView) findViewById(R.id.textViewArtistName);
        editTextTrackName = (EditText) findViewById(R.id.editTextTrackName);
        seekBarRating = (SeekBar) findViewById(R.id.seekBarRating);
        buttonAddTrack = (Button) findViewById(R.id.buttonAddTrack);

        listViewTracks = (ListView) findViewById(R.id.listViewTracks);

        // vamos mostrar o nome do artista, que foi selecionado na activity anterior
        // lembra que colocamos no intent ?
        // primeiro recuperamos aquele intent...
        Intent intent = getIntent();

        // agora recuperamos as strings que armazenamos no intent
        String id = intent.getStringExtra("ARTIST_ID");
        String name = intent.getStringExtra("ARTIST_NAME");

        // agora podemos colocar o nome do artista na interface
        textViewArtistName.setText(name);

        // definiremos a referência para o local (chave) debaixo da qual vamos
        // gravar as coisas no firebase.
        // Porém, diferentemente do que fizemos com os artistas (que ficavam
        // diretamente "debaixo" da chave "Artists", aqui, além da chave "Tracks",
        // teremos também a chave com o id do artista. Isto é, todas as músicas
        // de um determinado artista ficarão "debaixo" de uma chave com seu id.
        // Aqui, portanto, estamos definindo o local principal (getReference) como
        // Tracks, e debaixo dele, um filho com chave que é o id do artista.
        // Se nada disso existe ainda no banco de dados, será criado.
        databaseTracks = FirebaseDatabase.getInstance().getReference("Tracks").child(id);

        // definindo o tratador do evento de clique no botão
        buttonAddTrack.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                // método para gravar os dados da trilha (mais embaixo)
                saveTrack();
            }
        });
    }

    // método para gravar os dados de uma trilha no banco de dados
    private void saveTrack() {

        // vamos pegar os valores da interface, para criar uma nova trilha
        String trackName = editTextTrackName.getText().toString().trim();
        int rating = seekBarRating.getProgress();

        // verifica se o nome está vazio
        if(!TextUtils.isEmpty(trackName)) {

            // não está vazio;
            // vamos então gerar um id único desta trilha, vai ser a chave para
            // gravação de dados no banco de dados;
            // detalhes desses métodos podem ser vistos no código comentado no post 4.
            // Mutatis mutandis, é a mesma coisa que fizemos para gravar um artista.
            String id = databaseTracks.push().getKey();

            // já temos id, nome da trilha e o rating...
            // podemos criar o objeto Track
            Track track = new Track(id, trackName, rating);

            // enfim, gravamos o objeto no banco de dados, "debaixo" da chave id
            databaseTracks.child(id).setValue(track);

            // sucesso! toast falando disso
            Toast.makeText(this, "Trilha gravada com sucesso!", Toast.LENGTH_LONG).show();

        }
        else {

            // caixa vazia, vamos mostrar um toast com o erro
            Toast.makeText(this, "Precisa digitar o nome da trilha!", Toast.LENGTH_SHORT).show();
        }
    }
}

Executando nosso app, vamos clicar em “David Gilmour”, e adicionar uma música:


Vamos ver como fica no banco de dados:


Analisando:

O artista “David Gilmour” tem como id o valor “KmEU...”, localizado diretamente debaixo da chave “Artists” [na figura abaixo, legenda “1”]

A música que inserimos no banco de dados (“Wish you were here”), recebeu como id o valor “Kmr5...” [na figura abaixo, legenda “2”].

Note que ela foi inserida DEBAIXO de uma chave igual a chave do artista (“KmEU...”), e esta por sua vez, está debaixo da chave “Tracks”.


Vamos inserir mais uma música para este artista:


Olhando no banco de dados...


Pra fechar: vamos adicionar uma música a um outro artista, e ver como ficam os dados no banco:



To be continued...



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

Recuperando dados


Sempre lembrando que esta série de postagens é fortemente baseada neste post, sendo esta tradução/expansão autorizada pelo autor original.

Vamos agora recuperar os dados de Artistas, armazenados no Firebase, e exibir em uma lista (ListView).

Como já falamos anteriormente, não é nosso objetivo aqui dar detalhes do desenvolvimento Android, já que nosso foco é nas operações de dados do Firebase. Desta forma, não daremos grandes detalhes ou explicações sobre como utilizar as listas, a menos deste pequeno resumo:

  • cria-se um arquivo de layout, com a definição das Views que formarão cada “item” (linha) da lista;
  • cria-se um “Adapter”, uma classe que recupera o layout acima, “infla” ele (isto é, transforma numa View que será usada em cada item da lista), e preenche com os dados desejados;
  • finalmente, configura-se a lista para utilizar os serviços do Adapter criado acima para exibir os dados.

Começaremos então pela criação do layout do elemento gráfico que será usado como “item” da nossa lista. Crie um novo arquivo de layout (botão-direito na pasta “layout”, new, layout resource file, nome do arquivo layout_artist_item.xml),e utilize o código abaixo:


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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textViewName"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView" />

    <TextView
        android:id="@+id/textViewGenre"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView" />

</LinearLayout>

Trata-se apenas de dois “TextViews” (id's textViewName e textViewGenre), sendo o primeiro com um tamanho maior.


Vamos agora criar a classe do Adapter, responsável pela criação dos itens da lista.
Botão-direito no pacote (dentro de “Java”), new | Java Class.
Nome da classe: ArtistListAdapter.

Segue o código (comentado) da classe (sem imports e sem o package):


public class ArtistListAdapter extends ArrayAdapter<Artist> {

    private Activity context;
    private List<Artist> artistList;  // lista para armazenar os artitas

    public ArtistListAdapter(Activity context, List<Artist> artistList) {

        super(context, R.layout.layout_artist_item, artistList);
        this.context = context;
        this.artistList = artistList;
    }

    // método que é chamado para fornecer cada item da lista
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        // criando um objeto "inflador"
        LayoutInflater inflater = context.getLayoutInflater();

        // usando o inflador para criar uma View a partir do arquivo de layout
        // que fizemos definindo os itens da lista
        View listViewItem = inflater.inflate(R.layout.layout_artist_item, null, true);

        // pegando referências para as views que definimos dentro do item da lista,
        // isto é, os 2 textviews
        TextView textViewName = (TextView) listViewItem.findViewById(R.id.textViewName);
        TextView textViewGenre = (TextView) listViewItem.findViewById(R.id.textViewGenre);

        // a posição do artista na lista (armazenamento) é a mesma na lista (listview)
        // então usamos esse valor (position) para acessar o objeto "Artist" correto
        // dentro da lista artistList
        Artist artist = artistList.get(position);

        // finalmente, colocamos os valores do objeto artista recuperado
        // nas views que formam nosso item da lista
        textViewName.setText(artist.getArtistName());
        textViewGenre.setText(artist.getArtistGenre());

        // a view está pronta! É só devolver para quem pediu
        return listViewItem;
    }
}

Próximo passo, inserir uma ListView no arquivo activity_main.xml.
É trivial, portanto não repetirei o código aqui.
Id desta ListView: listViewArtists.


Vamos agora alterar nossa classe MainActivity, para preencher a lista com os dados armazenados no Firebase.

Em resumo:

  • teremos uma lista de objetos da classe Artist, para armazenar os dados que serão recuperados do Firebase;
  • definiremos um método para responder ao evento “houve alteração de dados no firebase”.
    Ou seja, quando houver alguma alteração de dados no firebase (neste caso, na parte da árvore que aponta para a chave “Artists”), automaticamente este método será chamado.
  • Será neste método que teremos então:
    • o código para receber os dados do Firebase;
    • gerar objetos da classe Artist a partir desses dados;
    • armazenar esses objetos numa lista;
    • criar um adapter, passando esta lista;
    • definir este adapter como o exibidor de dados da ListView que inserimos na tela principal.

Segue abaixo o novo código da classe MainActivity (sem package e sem imports).
Removi os comentários que apareceram numa postagem anterior, e coloquei novos, relativos as mudanças apresentadas aqui.


public class MainActivity extends AppCompatActivity {

    EditText editTextName;
    Button buttonAddArtist;
    Spinner spinnerGenres;

    DatabaseReference databaseArtists;

    // para acessar nossa nova ListView
    ListView listViewArtists;

    // lista para armazenar os objetos "Artist" que leremos do banco de dados
    // obs: por enquanto, só a referência...
    List<Artist> artistList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

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

        databaseArtists = FirebaseDatabase.getInstance().getReference("Artists");

        editTextName = (EditText) findViewById(R.id.editTextName);
        buttonAddArtist = (Button) findViewById(R.id.buttonAddArtist);
        spinnerGenres = (Spinner) findViewById(R.id.spinnerGenres);

        // acessando nossa nova ListView no arquivo de layout...
        listViewArtists = (ListView) findViewById(R.id.listViewArtists);

        // efetivamente criando a lista que vai armazenar os artistas
        // que leremos do banco de dados
        artistList = new ArrayList<>();

        buttonAddArtist.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                addArtist();
            }
        });
    }

    // lembrando o ciclo de vida de um app android, o primeiro método
    // que é executado é o onCreate, geralmente para carregar os layouts
    // e inicializar as coisas.
    //
    // logo a seguir, é chamado o método onStart. Este método também é chamado
    // quando o app estava em background e volta a ter foco. Logo, colocaremos aqui
    // o código para recuperar os dados do firebase e colocar na lista,
    // pois pode ter havido alteração nos dados quando o app estava em background
    // (por outra cópia em execução do nosso app, por exemplo).
    @Override
    protected void onStart() {

        super.onStart();

        // criando um tratador de eventos relacionado com nosso
        // banco de dados Firebase
        databaseArtists.addValueEventListener( new ValueEventListener() {

            // método chamado automaticamente quando houver mudança nos dados
            // armazenados no firebase
            // lembre-se!! databaseArtist "aponta" para a chave "Artists" no
            // JSON dos dados no firebase. Então, se algo mudar "ali dentro",
            // (isto é, dados de artistas), este método será chamado.
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

                // se entrou aqui, é porque mudou alguma coisa nos artistas
                // que estão no banco de dados. Então, vamos limpar a lista
                // que armazena esses artistas, para recuperar esses dados
                // novamente (já que não sabemos exatamente o que mudou)
                artistList.clear();

                // Recebemos um objeto DataSnapshot, que tem os dados
                // apontados por nossa referencia no firebase, isto é,
                // os dados que estão "debaixo" da chave "Artists".
                // Vamos então "varrer" esse objeto, pegando os dados lá dentro
                // e criando objetos da nossa classe Artist, para colocar na lista
                for(DataSnapshot artistSnapshot : dataSnapshot.getChildren()) {

                    // artistSnapshot tem um dos "filhos" de "Artists", isto é,
                    // tem os dados de um artista.
                    // Vamos então criar um objeto artista, a partir desses dados
                    //
                    // ... getValue(Artist.class)
                    //     pegue os dados, e a partir deles crie um objeto da
                    //     classe Artist.
                    Artist artist = artistSnapshot.getValue(Artist.class);

                    // enfim, colocamos o objeto artista criado a partir dos dados lidos
                    // na nossa lista de artistas
                    artistList.add(artist);
                }

                // agora que temos nossa lista de artistas atualizada,
                // podemos criar o adapter que vai ser responsável por
                // colocar esses dados no ListView,
                // passando nossa lista para este adapter
                ArtistListAdapter adapter = new ArtistListAdapter(MainActivity.this, artistList);

                // finalmente, informamos ao ListView quem é o adapter que vai
                // exibir os dados
                listViewArtists.setAdapter(adapter);
            }

            // método chamado no caso de algum erro no banco de dados
            // (ainda dentro do listener).
            // Não definiremos nada neste momento.
            @Override
            public void onCancelled(DatabaseError databaseError) {
            }

        });
    }

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

    private void addArtist() {

        String name = editTextName.getText().toString().trim();
        String genre = spinnerGenres.getSelectedItem().toString();

        if( !TextUtils.isEmpty(name) ) {

            String id = databaseArtists.push().getKey();
            Artist artist = new Artist(id, name, genre);
            databaseArtists.child(id).setValue(artist);

            Toast.makeText(this, "Artista adicionado", Toast.LENGTH_LONG).show();
        }
        else {

            Toast.makeText(this, "Você deve digitar um nome", Toast.LENGTH_LONG).show();
        }
    }
}

Lembrando como está nosso banco de dados...


Executando o app...


Inserindo dados...




Os novos dados são exibidos automaticamente!


Funciona também se inserir dados diretamente no "console" do Firebase (simulando, por exemplo, que foi outra cópia do app, em outro celular, que inseriu os dados):



Um último detalhe, sobre o método getValue, conforme nos diz a documentação:

“Este método é usado para organizar os dados contidos em um snapshot para dentro de uma classe da sua escolha.
Esta classe deve respeitar 2 requisitos:

  • A classe deve ter um construtor default que não receba nenhum argumento.
  • A classe deve definir getters públicos para os atributos que serão utilizados.”

Dêem uma conferida na classe Artist, para ver que esses requisitos são preenchidos (já falamos isso anteriormente).


Segue para parte 6...



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...