quinta-feira, 20 de julho de 2017

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

Exibindo as músicas em uma ListView


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.

Na postagem anterior, vimos como adicionar uma música (trilha, "track"), relacionada com o artista ao qual ela pertence. Neste post, iremos alterar a activity das trilhas para exibir uma lista de trilhas do artista selecionado.

Na verdade, não há nada de muito novo nesta etapa. O código é, mutatis mutandis, o mesmo que já mostramos em um post anterior, quando apresentamos todos os artistas cadastrados no banco de dados em uma lista (activity principal).

Porém, é importante ressaltar uma diferença importante entre as duas activities. Relembrando, temos no banco de dados duas "chaves" principais (quando eu falar em "chave", lembre-se que os dados estão sendo armazenados numa árvore JSON, conforme mostrado no primeiro post da série): a chave "Artists" e a chave "Tracks":


A diferença a ser lembrada é que, na activity principal, nossa referência ao banco de dados apontava diretamente para a chave Artists, pois todo acesso (pra gravar ou ler artistas) era feito diretamente "debaixo" desta chave:


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

Já na activity que trabalha com as músicas (tracks), nossa referência ao banco de dados aponta para O FILHO da chave Tracks que representa o "id" do artista, já que as músicas são acessadas a partir de um artista específico:


databaseTracks = FirebaseDatabase.getInstance().getReference("Tracks").child(id);

Logo, no código que apresentaremos aqui, no caso do método que é chamado quando houver mudança nos dados, os dados que são passados para este método são as músicas de um artista específico, que iremos inserir numa lista para exibir na listView.

Segue abaixo o arquivo .xml do layout dos itens da lista de músicas (tracks). Este arquivo tem o nome de layout_track_item.xml, e possui dois TextViews, denominados textViewName (pra exibir o nome da música) e textViewRating (para exibir a "nota" - rating - da música, definida na hora da criação).

<?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/textViewRating"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView" />

</LinearLayout>

Apresentaremos agora o código da classe adapter, responsável por criar os itens da lista, utilizando o layout apresentado acima. Como já falamos anteriormente, é um código praticamente igual ao que existe relativo ao adapter que cria a lista de artistas, apenas trocando da classe "Artist" para a classe "Track", trocando os nomes das views no layout que vai montar cada item da lista, e obviamente trocando nomes de algumas variáveis, para ficarem mais adequados ao propósito deste adapter (por exemplo, de "artistlist" para "tracklist').

Segue abaixo, então, o código da classe TrackListAdapter:

public class TrackListAdapter extends ArrayAdapter<Track> {

    private Activity context;
    private List<Track> trackList;  // lista para armazenar as tracks

    public TrackListAdapter(Activity context, List<Track> trackList) {

        super(context, R.layout.layout_track_item, trackList);
        this.context = context;
        this.trackList = trackList;
    }

    // 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_track_item, null, true);

        // pegando referências para as views que definimos dentro do item da lista,
        // isto é, os 2 textviews: nome e "rating" da trilha (música)
        TextView textViewName = (TextView) listViewItem.findViewById(R.id.textViewName);
        TextView textViewRating = (TextView) listViewItem.findViewById(R.id.textViewRating);

        // a posição da trilha na lista (armazenamento) é a mesma na lista (listview)
        // então usamos esse valor (position) para acessar o objeto "Track" correto
        // dentro da lista trackList
        Track track = trackList.get(position);

        // finalmente, colocamos os valores do objeto track recuperado
        // nas views que formam nosso item da lista
        textViewName.setText(track.getTrackName());
        //     o rating é numérico (int), convertemos para string para
        //     colocar no textView
        textViewRating.setText(String.valueOf(track.getTrackRating()));

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

Finalmente, apresentamos o código atualizado da classe AddTrackActivity, programando um "listener" para preencher a lista quando houver mudança nos dados no banco de dados no Firebase. Lembrando mais uma vez que a referência ao Firebase já aponta para a chave "Tracks", e debaixo dela para o "id" do artista em questão (escolhido na activity principal). Então a lista de músicas que o listener vai receber é composta apenas pelas músicas deste artista específico.

Novamente, com fizemos anteriormente, retirei comentários anteriores, e coloquei comentários apenas pertinentes a parte atualizada do código:


public class AddTrackActivity extends AppCompatActivity {

    TextView textViewArtistName;
    EditText editTextTrackName;
    SeekBar seekBarRating;
    Button buttonAddTrack;
    ListView listViewTracks;

    DatabaseReference databaseTracks;

    // variável para armazenar a lista de trilhas que leremos
    // do banco de dados
    List<Track> trackList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

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

        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);

        Intent intent = getIntent();
        String id = intent.getStringExtra("ARTIST_ID");
        String name = intent.getStringExtra("ARTIST_NAME");

        // vamos efetivamente criar a lista para armazenar as tracks
        trackList = new ArrayList<>();

        textViewArtistName.setText(name);

        // relembrando... databaseTracks aponta para o nó "id", debaixo de "Tracks",
        // isto é, aponta APENAS para os tracks (músicas) do artista "id"
        databaseTracks = FirebaseDatabase.getInstance().getReference("Tracks").child(id);

        buttonAddTrack.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {

                saveTrack();
            }
        });
    }

    // lembrando... usaremos aqui o método onStart() para poder atualizar a lista
    // de trilhas, no caso do app ficar em segundo plano, e depois ser chamado
    // novamente. Rever o ciclo de vida de um app android para entender melhor
    // este ponto, que também já foi discutido em um post anterior.
    @Override
    protected void onStart() {

        super.onStart();

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

            // método chamado automaticamente quando houver mudança nos dados
            // armazenados no firebase
            // lembre-se!! databaseTracks "aponta" para a chave "id" do artista
            //  selecionado, debaixo da chave "Tracks" no JSON dos dados no firebase.
            // Então, se algo mudar "ali dentro",
            // (isto é, dados de trilhas deste artista específico), este método será chamado.
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

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

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

                    // trackSnapshot tem um dos "filhos" de "Tracks", isto é,
                    // tem os dados de uma trilha.
                    // Vamos então criar um objeto trilha, a partir desses dados
                    //
                    // ... getValue(Track.class)
                    //     pegue os dados, e a partir deles crie um objeto da
                    //     classe Track.
                    Track track = trackSnapshot.getValue(Track.class);

                    // enfim, colocamos o objeto trilha criado a partir dos dados lidos
                    // na nossa lista de trilhas
                    trackList.add(track);
                }

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

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

            @Override
            public void onCancelled(DatabaseError databaseError) {
            }

        });
    }

    private void saveTrack() {

        String trackName = editTextTrackName.getText().toString().trim();
        int rating = seekBarRating.getProgress();

        if(!TextUtils.isEmpty(trackName)) {

            String id = databaseTracks.push().getKey();
            Track track = new Track(id, trackName, rating);
            databaseTracks.child(id).setValue(track);
            Toast.makeText(this, "Trilha gravada com sucesso!", Toast.LENGTH_LONG).show();
        }
        else {

            Toast.makeText(this, "Precisa digitar o nome da trilha!", Toast.LENGTH_SHORT).show();
        }
    }
}

Lembrando o estado do nosso banco de dados: Debaixo da chave "Artists", temos o artista "David Gilmour", com id = KmEU... . As músicas dele estão debaixo da mesma chave, "KMEU...", que por sua vez está debaixo da chave "Tracks". Nossa referência para o Firebase, na activity relacionada com as trilhas, aponta então para "Tracks/KmEU...". Ou seja, toda gravação ou leitura de "tracks" acontecerá debaixo deste caminho.


Ao rodar a aplicação, ela apresenta os artistas cadastrados:


Clicando em "David Gilmour", apontaremos para o id deste artista, debaixo da chave tracks, e então serão recuperadas todas as músicas deste artista, que serão exibidas na listView correspondente:


To be continued...




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:



Segue para parte 7...



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