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



Nenhum comentário: