3.2.2 RD-IntelliJ: Accés a les dades
Guardar dades
Disposem del mètode setValue() de la referència a la dada a la que volem accedir. Accepta 2 paràmetres:
- El primer és el valor que volem introduir.
- El segon és un listener per a poder sincronitzar. Ens farà falta sincronitzar únicament en els programes mode text senzills que fem. Quan fem els gràfics no ens farà falta, i posarem null
Si per exemple vulguérem guardar en la variable a1, de forma senzilla ho faríem així (ens funcionaria en els gràfics):
refA1.setValue("Valor per a a1", null);
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 cas que vulguem guardar no en l'arrel, sinó més avall en l'estructura JSON, disposem del mètode child(), que ens permet anar a un determinat fill, a l'estil de la segona manera descrita en el subpunt anterior. Per exemple si vulguérem canviar l'edat del primer empleat, l'estructura per a arribar seria: empresa --> empleat --> 0 --> edat
empresa.child("empleat").child("0").child("edat").setValue("33", null);
Si no existia abans qualsevol dels nodes de l'estructura, el crearà. Fins i tot, si no existira abans de la sentència anterior empresa, doncs crearia empresa, i dins d'ell empleat, i dins d'ell 0, i dins d'ell edat, amb el valor 33.
També podríem guardar tot un objecte, i es convertirà en estructura JSON, però ens ho deixem per a més avant.
Però com havíem dit abans, s'ha de sincronitzar amb Firebase, des del programa Java ens hem d'esperar a aquesta sincronització, si no no ens funcionarà. En el segon paràmetre ens posem un listener. Aquest exemple ja està complet. Guardeu-lo amb el nom Exemple_7_3_1_FirebaseRD_Guardar.kt
import java.io.FileInputStream
import com.google.firebase.FirebaseOptions
import com.google.auth.oauth2.GoogleCredentials
import com.google.firebase.FirebaseApp
import com.google.firebase.database.FirebaseDatabase
import java.util.concurrent.CountDownLatch
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.DatabaseError
fun main() {
val serviceAccount = FileInputStream("acces-a-dades-6e5a6-firebase-adminsdk-ei7uc-fcf7da56aa.json")
val options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://acces-a-dades-6e5a6.firebaseio.com").build()
FirebaseApp.initializeApp(options)
val empresa = FirebaseDatabase.getInstance().getReference("empresa")
val done = CountDownLatch (1)
empresa.child("empleat").child("0").child("edat").setValue("33",
object : DatabaseReference.CompletionListener {
override fun onComplete(p0: DatabaseError?, p1: DatabaseReference) {
done.countDown()
}
})
done.await()
}
Recordeu que heu de canviar el nom del fitxer json i la URL per les vostres.
I aquest és el resultat d'executar-lo. A la dreta podem veure com s'està modificant la dada que preteníem:

Però com comentàvem, en el cas de les aplicacions gràfiques resulta més senzill, ja que no haurem d'esperar expressament a la sincronització, sinó que l'aplicació es queda en marxa, i per tant no hi haurà problema.
Ho practicarem en un exemple nou, on arribarem a construir un Xat, i ens servirà per a practicar totes les coses que us volem mostrar en Realtme Database de Firebase.
Aquest és l'esquelet del programa. Guardeu-lo amb el nom Exemple_7_3_2_FirebaseRD_CrearXat.kt:
import java.awt.EventQueue
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JTextArea
import javax.swing.JButton
import javax.swing.JTextField
import java.awt.BorderLayout
import javax.swing.JPanel
import java.awt.FlowLayout
import java.awt.Color
import javax.swing.JScrollPane
import java.io.FileInputStream
import com.google.firebase.FirebaseOptions
import com.google.auth.oauth2.GoogleCredentials
import com.google.firebase.FirebaseApp
import com.google.firebase.database.*
class CrearXat : JFrame() {
val etUltimMissatge= JLabel("Últim missatge: ")
val ultimMissatge= JLabel()
val etiqueta = JLabel("Missatges:")
val area = JTextArea()
val etIntroduccioMissatge = JLabel("Introdueix missatge:")
val enviar = JButton("Enviar")
val missatge = JTextField(15)
// en iniciar posem un contenidor per als elements anteriors
init {
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
setBounds(100, 100, 450, 300)
setLayout(BorderLayout())
// contenidor per als elements
//Hi haurà títol. Panell de dalt: últim missatge. Panell de baix: per a introduir missatge. Panell central: tot el xat
val panell1 = JPanel(FlowLayout())
panell1.add(etUltimMissatge)
panell1.add(ultimMissatge)
getContentPane().add(panell1, BorderLayout.NORTH)
val panell2 = JPanel(BorderLayout())
panell2.add(etiqueta, BorderLayout.NORTH)
area.setForeground(Color.blue)
area.setEnabled(false)
val scroll = JScrollPane(area)
panell2.add(scroll, BorderLayout.CENTER)
getContentPane().add(panell2, BorderLayout.CENTER)
val panell3 = JPanel(FlowLayout())
panell3.add(etIntroduccioMissatge)
panell3.add(missatge)
panell3.add(enviar)
getContentPane().add(panell3, BorderLayout.SOUTH)
setVisible(true)
enviar.addActionListener{enviar()}
val serviceAccount = FileInputStream("acces-a-dades-6e5a6-firebase-adminsdk-ei7uc-fcf7da56aa.json")
val options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://acces-a-dades-6e5a6.firebaseio.com").build()
FirebaseApp.initializeApp(options)
// Exemple de listener de lectura única addListenerForSingleValue()
// 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
}
// Exemple de guardar dades sense haver d'esperar per ser una aplicació gràfica
// Per a guardar dades. Sobre a1, i despŕes sobre la llista xat
fun enviar(){
}
}
fun main(args: Array<String>) {
EventQueue.invokeLater {
CrearXat().isVisible = true
}
}
Recordeu que heu de canviar el nom del fitxer json i la URL per les vostres. Ho podeu copiar de l'exemple anterior
Observeu que ja tenim col·locades en l'anterior programa les dades de connexió:
val serviceAccount = FileInputStream("acces-a-dades-6e5a6-firebase-adminsdk-ei7uc-fcf7da56aa.json")
val options = FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("https://acces-a-dades-6e5a6.firebaseio.com").build()
FirebaseApp.initializeApp(options)
I torne a insistir en què heu de canviar la referència al fitxer JSON i la URL.
Per a guardar les dades, en aquest exemple de moment guardarem en la clau de Firebase a1 en el moment de apretar el botó de baix d'Enviar. No farà falta muntar cap listener per veure si ja hem acabat, ja que el programa continua en marxa.
// Exemple de guardar dades sense haver d'esperar per ser una aplicació gràfica
// Per a guardar dades. Sobre a1, i despŕes sobre la llista xat
fun enviar(){
val refA1 = FirebaseDatabase.getInstance().getReference("a1")
refA1.setValue(missatge.getText(), null)
}
Observeu que ara queda molt senzilla la sentència de guardar, i no ha fet falta ficar cap listener en el segon paràmetre, sinó null, per estar en una aplicació gràfica:
refA1.setValue(missatge.getText(), null)
El resultat seria aquest, on es veu la modificació de la dada que volíem:

Recuperar dades
La lectura de dades és més complicada que l'escriptura. És en bona part per culpa de la "sincronització" de les dades que obtenim. Per això no existeix un mètode tan senzill com el getValue(). La lectura s'ha de muntar sempre amb Listeners, que es queden escoltant si hi ha alguna actualització de la dada registrada. Recordeu que és de la dada registrada, no de tota la Base de Dades.
Podem muntar dos tipus de Listeners, però el seu funcionament serà similar
- Els que només escolten per a llegir les dades al principi, i no esperaran per a posteriors canvis en les dades (i per tant no consumiran tants recursos): addListenerForSingleValueEvent()
- Els que es queden escoltant tota l'estona: addValueEventListener()
En ambdos 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 segon està sempre escoltant, i el primer 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
addListenerForSingleValue()
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 haurà d'estar creada prèviament des del mateix entorn de Firebase.

Modificarem el fragment de programa marcat pel comentari, i el que fem és esperar per a llegir només una vegada.
// Exemple de listener de lectura única addListenerForSingleValue()
// Per a posar el títol. Sobre nomXat
val nomXat = FirebaseDatabase.getInstance().getReference("nomXat")
nomXat.addListenerForSingleValueEvent(object : ValueEventListener {
override
fun onDataChange(dataSnapshot: DataSnapshot) {
setTitle(dataSnapshot.getValue().toString())
}
override
fun onCancelled(error: DatabaseError) {
}
})
Aquest és el resultat, i quan l'executeu observareu que tarda un poc en mostrar el títol. És perquè ho està llegint de Firebase.

addValueEventListener()
Anem a veure ara un exemple per a l'altre mètode, el addValueEventListener(), que és el que es queda escoltant tota la estona
Concretarem el que farem és escoltar tota la estona per si es produeix algun canvi en la clau a1. Si es produeix aquest canvi, modificarà el valor del JLabel ultimMissatge que està dalt.
També afegirem el missatge al JTextArea, per a que tinga apariència de xat, encara que després ho modificarem per a millorar-ho. Ho hem de col·locar on està marcat pel comentari
// Exemple de listener de lectura contínua addValueEventListener()
// Per a posar l'últim missatge registrat. Sobre a1
val ultim = FirebaseDatabase.getInstance().getReference("a1")
ultim.addValueEventListener(object : ValueEventListener {
override
fun onDataChange(dataSnapshot: DataSnapshot) {
ultimMissatge.setText(dataSnapshot.getValue().toString())
area.append(dataSnapshot.getValue().toString() + "\n") // aquesta línia després la llevarem
}
override
fun onCancelled(error: DatabaseError ) {
}
})
L'execució serà com la de la pantalla de dalt a l'esquerra. Però si es produeix algun canvi (com es mostra en la pantalla de la dreta), aquest canvi es reflectirà automàticament tant en el JLabel de dalt com en el TextArea, tal i com es mostra en la imatge de baix a l'esquerra:
|
|
![]() |
En canvi, una vegada està en marxa el programeta del xat, per més que canviem nomXat, que es traslladava al títol de la finestra, aquest títol ja no canviarà perquè recordem que es feia una única lectura

Com veieu ha estat molt fàcil construir una espècie de xat. Ara millorarem aquest xat.
Tractament de llistes
Per a explicar millor el tractament de llistes, crearem una altra referència a una clau que representarà una llista de missatges. Cada missatge constarà d'un nom i un contingut, i així vuerem també el tractament d'objectes.
El primer que ens haurem de definir és la referència a aquesta nova clau, que l'anomenarem xat (no està creada encara en la Base de Dades).
val xat = FirebaseDatabase.getInstance().getReference("xat")
Anar afegint elements a la llista, ho podem fer a mà, posant nosaltres l'índex, ja que hem vist que la manera de representar en Firebase una llista són fills únicament amb clau, que seria el subíndex.
Per tant una manera d'afegir el primer missatge del xat, seria amb l'índex 0. Posem aquest codi quan apretem el botó (no hem llevat de moment el fet de guardar en a1, per a que mentre fem les proves es veja tot el xat):
// Exemple de guardar dades sense haver d'esperar per ser una aplicació gràfica
// Per a guardar dades. Sobre a1, i despŕes sobre la llista xat
fun enviar() {
val refA1 = FirebaseDatabase.getInstance().getReference("a1")
refA1.setValue(missatge.getText(), null)
val xat = FirebaseDatabase.getInstance().getReference("xat")
xat.child("0").child("nom").setValue("Usuari1", null)
xat.child("0").child("contingut").setValue(missatge.getText(), null)
}
Quan apretem s'actualitzarà la Base de Dades d'aquesta manera:

Però aquesta manera d'introduir en la llista acaba per ser molt poc pràctica. Si ara anàrem a introduir un segon missatge (nom i contingut) li hauríem de posar com a índex 1. No és viable.
Podríem dur 2 polítiques per a gestionar els índex:
- Podríem portar un comptador per a saber quin índex toca inserir en cada moment, cosa també molt poc pràctica perquè si l'aplicació està instal·lada en més d'un dispositiu, podria haver col·lisió en el número d'índex.
- Podríem mirar quin és l'últim índex introduït, per a incrementar-lo en una unitat. Però açò suposa llegir de la Base de Dades, i com hem vist abans suposarà muntar un Listener, segurament dels d'un únic ús. Per tant se'ns complic la cosa. Es pot fer, però no és còmode.
Per a estalviar-nos aquesta feina, Firebase ens proporciona un métode per a afegir un nou element a una llista, el mètode push(). Introdueix un nou element a la llista, i li posa com a índex un numero generat de manera que no es repetirà mai. L'única preocupació que hem de tenir és guardar aquest índex (amb getKey()), per a poder posar-lo com a clau.
// Exemple de guardar dades sense haver d'esperar per ser una aplicació gràfica
// Per a guardar dades. Sobre a1, i despŕes sobre la llista xat
fun enviar() {
val refA1 = FirebaseDatabase.getInstance().getReference("a1")
refA1.setValue(missatge.getText(), null)
val xat = FirebaseDatabase.getInstance().getReference("xat")
val clau = xat.push().getKey()
xat.child(clau).child("nom").setValue("Usuari1", null)
xat.child(clau).child("contingut").setValue(missatge.getText(), null)
}
I el resultat és aquest:

Com veiem és una cadena molt llarga que no és repetirà mai.
Anem a fer una tercera inserció d'un missatge, però ara ho completarem més, i solucionarem de pas algun problemeta que podíem haver tingut.
Crearem 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. Per a aquest exemple segurament no valdria la pena l'esforç, però es pot veure la utilitat per a objectes més complexos.
Primer definim la classe. El millor és que el guardem com una classe nova, és a dir com a Missatge.kt :
class Missatge(var nom: String, var contingut: String)
Per a guardar, col·locaríem aquestes sentències entre les accions del clic del botó, com en les altres ocasions. Observeu com ara ni tan sols ens fa falta guardar en una variable el getKey(), ja que es fa tot només en una operació:
// Exemple de guardar dades sense haver d'esperar per ser una aplicació gràfica
// Per a guardar dades. Sobre a1, i despŕes sobre la llista xat
fun enviar() {
val refA1 = FirebaseDatabase.getInstance().getReference("a1")
refA1.setValue(missatge.getText(), null)
val xat = FirebaseDatabase.getInstance().getReference("xat")
val m = Missatge("Usuari1", missatge.getText())
xat.push().setValue(m, null)
}
El resultat seria el mateix que en l'ocasió anterior:

Ara només ens falta el tractament de lectura de les llistes.
addChildEventListener()
Podríem muntar un Listener com en les altres ocasions, però ara disposarem d'uns altres Listeners que se'ns acoplen millor ja que s'activen quan hi ha modificacions en algun element de la llista. En l'exemple només utilitzarem el de creació d'un nou element, però com veurem també podríem utilitzar els moments de supressió o modificació d'elements.
Es tracta del Listener ChildEventListener, i hem d'utilitzar el mètode addChildEventListener() sobre la llista. Voldrà la implementació dels mètodes: onChildAdded(), onChildChanged(), onChildRemoved() i onChildMoved(), a banda de onCancelled(). Però com comentàvem ara només utilitzarem la de creació d'un nou element, i senzillament el mostrarem en el TextView.
Al dataSnapshot arriba únicament l'element introduït, modificat o esborrat, no tota la llista. Per tant és molt còmode. També arriba el nom de l'element anterior com a segon paràmetre, per si hem de fer algun tractament. És important fixar-nos en que aquest segon paràmetre l'hem de definir com a String? (amb la interrogant per a contemplar el null). Si no posem la interrogant, no es comportarà bé la primera vegada.
Açò ens substituirà l'escriptura que féiem abans del TextArea (a mi m'havia quedat en la línia 95, vosaltres la tindreu aproximadament per ahí). Per tant llevem aquesta línia:
area.append(dataSnapshot.getValue().toString() + "\n"); // aquesta línia després la llevarem
I posem el següent on queda marcat pel comentari. Així ens quedarà un xat més "professional"
// Exemple de listener d'una llista addChildEventListener()
// Per a posar tota la llista de missatges. Sobre xat
val xat = FirebaseDatabase.getInstance().getReference("xat")
xat.addChildEventListener(object : ChildEventListener {
override
fun onChildAdded(dataSnapshot: DataSnapshot, s: String?) {
area.append(dataSnapshot.child("nom").getValue().toString() + ": "
+ dataSnapshot.child("contingut").getValue().toString() + "\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) {
}
}
)
Si executem aquest programa, tenint només en el clic del botó l'addició d'un element a la llista i aquest Listener anterior, veurem que inicialment ens apareixeran tots els elements de la llista. Això és perquè considera que inicialment s'afegeix cadascun dels elements de la llista, i en el mateix ordre en que estan definits. En aquest imatge hem aprofitat per afegir un quart missatge:

Llicenciat sota la Llicència Creative Commons Reconeixement NoComercial SenseObraDerivada 4.0


