Omet navegació

2.2.3.2 RD-Android: Accés a les dades

Una vegada vist un exemple, en el qual hem guardat dades i també les hem recuperades i hem vist els canvis en temps real, anem a veure més exemples de com accedir a les dades.

En l'exemple anterior hem accedit a una parella clau-valor situada en l'arrel de la Base de Dades.

Anem a veure alguns exemples de com accedir dins de l'estructura JSON guardada en Firebase. Com el que voldrem serà mostrar més informació, incorporarem un TextView per a mostrar més coses còmodament, i deixem el EditText i el Button per a poder introduir informacions.

Aquesta serà ara l'estructura del activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/ultim"
android:layout_width="382dp"
android:layout_height="39dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:text=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/area"
android:layout_width="0dp"
android:layout_height="0dp"
android:text=""
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ultim"
app:layout_constraintVertical_bias="0.261" />

<EditText
android:id="@+id/text"
android:layout_width="317dp"
android:layout_height="47dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/boto"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/ultim"
app:layout_constraintVertical_bias="1.0" />

<Button
android:id="@+id/boto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/text"
android:layout_centerHorizontal="true"
android:text="Enviar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.993"
app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Així les parts que tindrem, i que ens servirà per a mostrar cadascuna de les coses que volem, de forma similar que en Eclipse seran:

  • El títol de l'aplicació, que el modificarem amb un addListenerForSingleValueEvent()
  • El TextView de dalt, anomenat ultim, que el modifiquem per mig d'un addValueEventListener()
  • El TextView central que ocupa quasi tota la pantalla, anomenat area, que el modificarem amb la modificació de la llista addChildEventListener()
  • El EditText de baix, anomenat text, i el botó, que servirà per a afegir un nou element del xat

Aquest és l'esquelet del progama:

JAVA

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.database.ChildEventListener;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView ultim = (TextView)findViewById(R.id.ultim);
final TextView area = (TextView)findViewById(R.id.area);
final EditText text = (EditText)findViewById(R.id.text);

final Button boto = (Button)findViewById(R.id.boto);
boto.setText("Enviar");

// Referències a la Base de Dades i a les variables a1, nomXat i xat

// Exemple de guardar dades. Primer sobre a1, i despŕes sobre la llista xat

// Exemple de listener de lectura única addListenerForSingleValueEvent()
// Per a posar el títol. Sobre nomXat

// Exemple de listener de lectura contínua addValueEventListener()
// Per a posar l'últim missatge registrat. Sobre a1

// Exemple de listener d'una llista addChildEventListener()
// Per a posar tota la llista de missatges. Sobre xat

}
}

KOTLIN

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import com.google.firebase.database.FirebaseDatabase import com.google.firebase.database.DatabaseError import com.google.firebase.database.DataSnapshot import com.google.firebase.database.ValueEventListener class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) boto.text = "Enviar" // Referències a la Base de Dades i a les variables a1, nomXat i xat // Exemple de guardar dades. Primer sobre a1, i despŕes sobre la llista xat // Exemple de listener de lectura única addListenerForSingleValueEvent() // Per a posar el títol. Sobre nomXat // Exemple de listener de lectura contínua addValueEventListener() // Per a posar l'últim missatge registrat. Sobre a1 // Exemple de listener d'una llista addChildEventListener() // Per a posar tota la llista de missatges. Sobre xat } }

Referència a la Base de Dades i a les dades concretes a les quals volem accedir

Podem fer referència de forma individual a cadascuna de les "variables" guardades, és a dir a cadascuna de les parelles clau-valor. En l'exemple anterior havíem utilitzat aquest:

JAVA

FirebaseDatabase database = FirebaseDatabase.getInstance();

I per a les parelles clau-valor que utilitzarem en l'exemple:

final DatabaseReference refA1 = database.getReference("a1");
final DatabaseReference nomXat = database.getReference("nomXat");
final DatabaseReference xat = database.getReference("xat");

observeu com no és cap impediment que ens toque definir-les com a final, ja que aquesta referència no ha de canviar en cap moment després d'inicialitzar-la. Si que podem fer canviar el seu valor, però la referència sempre ha d'apuntar al mateix lloc.

KOTLIN

L'equivalent en Kotlin serà:

val database = FirebaseDatabase.getInstance()
val refA1 = database.getReference("a1")
val nomXat = database.getReference("nomXat")
val xat = database.getReference("xat")

Guardar dades

L'operació de guardar és més senzilla que la de recuperar. Disposem del mètode setValue() de la referència a la dada a la que volem accedir. En l'exemple de la pregunta anterior guardàvem la dada d'aquesta manera:

Per a JAVA:

refA1.setValue(text.getText().toString());

Per a KOTLIN:

refA1.setValue(text.text.toString())

En l'operació de guardar si la parella clau-valor on es va a guardar existia, doncs modificarà el valor. I si no existia, la crearà.

En el nostre exemple guardarem en el moment d'apretar el botó. Inicialment ho farem sobre a1, encara que quan vegem les llistes ho canviarem

JAVA

        // Exemple de guardar dades. Primes sobre a1, i despŕes sobre la llista xat
        boto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                refA1.setValue(text.getText().toString());
                text.setText("");
            }
        });

KOTLIN

        // Exemple de guardar dades. Primes sobre a1, i despŕes sobre la llista xat
        boto.setOnClickListener {
            refA1.setValue(text.text.toString())
            text.setText("")
        }

Recuperar dades

Recordem que podem muntar dos tipus de Listeners, però el seu funcionament serà similar

  • Els que escolten només una vegada al principi: addListenerForSingleValueEvent()
  • Els que es queden escoltant tota l'estona: addValueEventListener()

En ambdós casos obtenim com a paràmetre un DataSnapshot (còpia) de la dada registrada. I d'aquest tipus, DataSnapshot, sí que tenim el mètode getValue() per a accedir a la dada. Ambdós tipus de Listeners tenen un tractament absolutament similar, únicament amb la diferència abans esmentada que el primer està sempre escoltant, i el segon només escolta una vegada al principi.

El mètode getValue() admet un paràmetre que serà la classe del tipus que volem obtenir. Podem posar les següents:

  • String.class, i aleshores el que obtenim s'interpretarà com un String
  • Double.class, i s'interpretarà com un número real de doble precisió
  • Boolean.class, i s'interptretarà com un valor booleà.
  • També es poden posar classes per a obtenir tot un objecte (Map) i per a una llista (List). Fins i tot es podria arribar a posar una classe definida per nosaltres. Però amb els anteriors nosaltres en tindrem prou

Mirem la manera d'utilitzar una i altra. Ho apliquem a dues referències diferents, per a que pugueu comprovar que una sempre té efecte, mentre que l'altra només té efecte la primera vegada, i si després es modifiquen les dades, l'aplicació no s'enterarà.

addListenerForSingleValueEvent()

En aquest primer exemple anem a agafar una única vegada la dada que ens interessa, i per tant  utilitzarem addListenerForSingleValueEvent(). Ens servirà per a posar el títol de l'aplicació, i ho farem consultant la clau nomXat, que la tindrem creada des de la pregunta 2.2.2.2 de la part de Java:

JAVA

        // Exemple de listener de lectura única addListenerForSingleValueEvent()
        // Per a posar el títol. Sobre nomXat
        nomXat.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                String value = dataSnapshot.getValue(String.class);
                setTitle(value);
            }

            @Override
            public void onCancelled(DatabaseError error) {
            }
        });

KOTLIN

        // Exemple de listener de lectura única addListenerForSingleValue()
        // Per a posar el títol. Sobre nomXat
        nomXat.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val value = dataSnapshot.getValue(String::class.java)
                setTitle(value)
            }

            override fun onCancelled(error: DatabaseError) {
            }
        })



addValueEventListener()

És la que sempre està escoltant. Per tant qualsevol modificació en la Base de Dades es veurà reflectit, és a dir, canviarà el contingut del TextView ultim

JAVA

		// Exemple de listener de lectura contínua addValueEventListener()
		// Per a posar l'últim missatge registrat. Sobre a1
        refA1.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                String value = dataSnapshot.getValue(String.class);
                ultim.setText(value);
            }

            @Override
            public void onCancelled(DatabaseError error) {
            }
        });

KOTLIN

		// Exemple de listener de lectura contínua addValueEventListener()
		// Per a posar l'últim missatge registrat. Sobre a1
        refA1.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val value = dataSnapshot.getValue(String::class.java)
                ultim.setText(value)
            }

            override fun onCancelled(error: DatabaseError) {
            }
        })

Tractament de llistes

Ja vam veure en la pregunta 2.2.2.2 de la part de Java que la manera més pràtica d'inserir elements en una llista, per a no haver de dur un manteniment dels índex, era amb el mètode push(), i que per a no tenir efectes no desitjats era millor posar tot l'element d'una vegada. Així, si hem de posar el nom de l'usuari que farà l'entrada del xat i el contingut del missatge, millor crear un objecte que ho continga tot, i col·locar en la llista aquest objecte.

Crearem per tant una classe anomenada Missatge, que inclourà les propietats nom i contingut. Crearem un objecte Missatge amb uns nous valors, i veurem que el podem guardar perfectament.

Primer definim la classe. El millor és que el guardem com una classe nova, és a dir com a Missatge.java o Missatge.kt:

JAVA

public class Missatge{
public String nom;
public String contingut;

public Missatge(){
}

public Missatge(String nom,String cont){
this.nom=nom;
this.contingut=cont;
}
}

Haurem de tenir també els getters i setters.

KOTLIN

class Missatge(val nom: String, val contingut: String) {}

Per a guardar, col·locaríem aquestes sentències entre les accions del OnClik del botó, substituint les sentències que modificaven a1, perquè ara afegirem a la llista xat :

JAVA

        // Exemple de guardar dades. Primer sobre a1, i despŕes sobre la llista xat
        boto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                refA1.setValue(text.getText().toString());

                Missatge m = new Missatge("Usuari1",text.getText().toString());
                xat.push().setValue(m);

                text.setText("");
            }
        });


KOTLIN

        // Exemple de guardar dades. Primer sobre a1, i despŕes sobre la llista xat
        boto.setOnClickListener {
            refA1.setValue(text.text.toString())
            xat.push().setValue(Missatge("Usuari1",text.text.toString()))
            text.setText("")
        }	



Ara només ens falta el tractament de lectura de les llistes.

Utilitzarem el Listener ChildEventListener, i hem d'utilitzar el mètode addChildEventListener() sobre la llista. Voldrà la implementació dels mètodes: onChildAdded(), onChildChanged()onChildRemoved() i onChildMoved(), però nosaltres només utilitzarem onChildAdded()

Al dataSnapshot arriba únicament l'element introduït, modificat o esborrat, no tota la llista. Per tant és molt còmode. També arriba una referència a l'element anterior com a segon paràmetre, per si hem de fer algun tractament, cosa que en aquest exemple no ens fa falta:

JAVA

		// Exemple de listener d'una llista addChildEventListener()
		// Per a posar tota la llista de missatges. Sobre xat
		xat.addChildEventListener(new ChildEventListener() {
		    @Override
		    public void onChildAdded(DataSnapshot dataSnapshot, String s) {
		        area.append(dataSnapshot.child("nom").getValue(String.class) + ": " + dataSnapshot.child("contingut").getValue(String.class) + "\n");
		    }
		    @Override
		    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
		    }
		    @Override
		    public void onChildRemoved(DataSnapshot dataSnapshot) {
		    }
		    @Override
		    public void onChildMoved(DataSnapshot dataSnapshot, String s) {
		    }
		    @Override
		    public void onCancelled(DatabaseError databaseError) {
		    }
		});

KOTLIN

        // Exemple de listener d'una llista addChildEventListener()
        // Per a posar tota la llista de missatges. Sobre xat
        xat.addChildEventListener(object : ChildEventListener {
            override fun onChildAdded(dataSnapshot: DataSnapshot, s: String?) {
                area.append(
                    dataSnapshot.child("nom").getValue(String::class.java) + ": " + dataSnapshot.child("contingut").getValue(String::class.java) + "\n"                    );
            }
            override fun onChildChanged(dataSnapshot: DataSnapshot, s: String?) {
            }
            override fun onChildRemoved(dataSnapshot: DataSnapshot) {
            }
            override fun onChildMoved(dataSnapshot: DataSnapshot, s: String?) {
            }
            override fun onCancelled(databaseError: DatabaseError) {
            }
        })

Aquest seria el resultat: