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




6 comentários:

Unknown disse...

show de bola amigo

Carlos José Pereira disse...

Obrigado!
Preciso terminar, falta umas coisinhas ainda pra ficar completo.
Grande abraço!
Carlão

Everton Ap disse...

Olá amigo, ótimo post tutorial!
Ainda pretende concluir?

Carlos José Pereira disse...

Oi Everton, pretendo sim... mas não tenho previsão, estou atolado de coisas.
Forte abraço!
Carlão

Profº Pedro Celso disse...

E o que tem até este momento funciona?

Carlos José Pereira disse...

Oi Pedro, é pra estar funcionando... faz um tempinho que não mexo com isso, li que houve algumas mudanças no firebase, mas como esse tutorial aborda recursos bem básicos, acredito que esteja tudo funcionando ainda. Forte abraço!