Omet navegació

3.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 dos TextView per a mostrar més coses còmodament, i un EditText i un 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 a la de IntelliJ, 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:

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

Nota

Recordeu que per a que ens reconega tots els elements del layout, al principi del build.gradle de l'aplicació heu d'afegir la següent línia :

id 'kotlin-android-extensions'

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:

        // Referències a la Base de Dades i a les variables a1, nomXat i xat
        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. Si la parella clau-valor on es va a guardar ja 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

        // 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 3.2.2 de la part de IntelliJ:

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

		// 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 3.2.2 de la part de IntelliJ 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.kt:

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 :

        // 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:

        // 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: