¿Cómo devolver el valor de dataSnapshot como resultado de un método?

No tengo mucha experiencia con Java. No estoy seguro de si esta pregunta es estúpida, pero necesito obtener un nombre de usuario de Firebase Realtime Database y devolver este nombre como resultado de este método. Entonces … descubrí cómo obtener este valor, pero no entiendo cómo devolverlo como resultado de este método. ¿Cuál es la mejor manera de hacer esto?

private String getUserName(String uid) { databaseReference.child(String.format("users/%s/name", uid)) .addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // How to return this value? dataSnapshot.getValue(String.class); } @Override public void onCancelled(DatabaseError databaseError) {} }); } 

Este es un problema clásico con las API web asíncronas. No puede devolver algo ahora que aún no se ha cargado. En otras palabras, no puede simplemente crear una variable global y usarla fuera del método onDataChange() porque siempre será null . Esto está sucediendo porque el método onDataChange() se llama asíncrono. Dependiendo de la velocidad de conexión y el estado, puede tomar desde unos pocos cientos de milisegundos hasta unos segundos antes de que los datos estén disponibles.

Pero Firebase Realtime Database no solo carga los datos de manera asincrónica, casi todas las otras API web modernas, ya que puede llevar algo de tiempo. Por lo tanto, en lugar de esperar los datos (lo que puede llevar a diálogos de aplicación que no responden a sus usuarios), el código de la aplicación principal continúa mientras los datos se cargan en una secuencia secundaria. Luego, cuando los datos estén disponibles, se llama al método onDataChange () y puede usar los datos. En otras palabras, cuando el método onDataChange() se llama, sus datos aún no están cargados.

Tomemos un ejemplo, colocando algunas declaraciones de registro en el código, para ver más claramente lo que está sucediendo.

 private String getUserName(String uid) { Log.d("TAG", "Before attaching the listener!"); databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // How to return this value? dataSnapshot.getValue(String.class); Log.d("TAG", "Inside onDataChange() method!"); } @Override public void onCancelled(DatabaseError databaseError) {} }); Log.d("TAG", "After attaching the listener!"); } 

Si ejecutamos este código, la salida será:

¡Antes de conectar al oyente!

¡Después de unir al oyente!

¡Dentro del método onDataChange ()!

Probablemente esto no sea lo que esperaba, pero explica con precisión por qué sus datos son null al devolverlos.

La respuesta inicial para la mayoría de los desarrolladores es intentar “arreglar” este asynchronous behavior , que personalmente recomiendo contra esto. La web es asincrónica, y cuanto antes acepte eso, más pronto podrá aprender a ser productivo con las API web modernas.

Encontré más fácil replantear problemas para este paradigma asincrónico. En lugar de decir “Primero obtenga los datos, luego inicie sesión”, marco el problema como “Comience a obtener datos. Cuando los datos estén cargados, conéctelos”. Esto significa que cualquier código que requiera los datos debe estar dentro del método onDataChange() o ser llamado desde adentro, de esta manera:

 databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // How to return this value? if(dataSnapshot != null) { System.out.println(dataSnapshot.getValue(String.class)); } } @Override public void onCancelled(DatabaseError databaseError) {} }); 

Si quieres usar eso afuera, hay otro enfoque. Debes crear tu propia callback para esperar a que Firebase te devuelva los datos. Para lograr esto, primero debe crear una interface como esta:

 public interface MyCallback { void onCallback(String value); } 

Entonces necesita crear un método que realmente obtenga los datos de la base de datos. Este método debería verse así:

 public void readData(MyCallback myCallback) { databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { String value = dataSnapshot.getValue(String.class); myCallback.onCallback(value); } @Override public void onCancelled(DatabaseError databaseError) {} }); } 

Al final, simplemente llame readData() método readData() y pase una instancia de la interfaz MyCallback como argumento donde lo necesite de esta manera:

 readData(new MyCallback() { @Override public void onCallback(String value) { Log.d("TAG", value); } }); 

Esta es la única forma en que puede usar ese valor fuera del método onDataChange() . Para obtener más información, también puede echar un vistazo a este video .

Creo que entiendo lo que estás preguntando. Aunque diga que quiere “devolverlo” (per se) del método fetch, bastará con decir que realmente solo quiere poder usar el valor recuperado después de que se haya completado su búsqueda. Si es así, esto es lo que debes hacer:

  1. Crea una variable en la parte superior de tu clase
  2. Recupere su valor (que ha hecho en su mayoría correctamente)
  3. Establezca la variable pública en su clase igual al valor recuperado

Una vez que tu búsqueda tiene éxito, puedes hacer muchas cosas con la variable. 4a y 4b son algunos ejemplos simples:

4a. Editar: como un ejemplo de uso, puede desencadenar cualquier otra cosa que necesite ejecutar en su clase que use yourNameVariable (y puede estar seguro de que su yourNameVariable no es nulo)

4b. Editar: como ejemplo de uso, puede usar la variable en una función que se activa con un botón enClickListener.


Prueba esto.

 // 1. Create a variable at the top of your class private String yourNameVariable; // 2. Retrieve your value (which you have done mostly correctly) private void getUserName(String uid) { databaseReference.child(String.format("users/%s/name", uid)) .addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // 3. Set the public variable in your class equal to value retrieved yourNameVariable = dataSnapshot.getValue(String.class); // 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null. sayHiToMe(); } @Override public void onCancelled(DatabaseError databaseError) {} }); } // (part of step 4a) public void sayHiToMe() { Log.d(TAG, "hi there, " + yourNameVariable); } // 4b. use the variable in a function triggered by the onClickListener of a button. public void helloButtonWasPressed() { if (yourNameVariable != null) { Log.d(TAG, "hi there, " + yourNameVariable); } } 

Luego, puedes usar yourNameVariable donde quieras en toda tu clase.


Nota: solo asegúrate de verificar que yourNameVariable no sea nulo cuando lo onDataChange ya que onDataChange es asincrónico y es posible que no se haya completado en el momento en que intentes utilizarlo en otro lugar.

Aquí hay una idea loca, dentro de DataChange, ponlo dentro de un TextView con visibilidad ido textview.setVisiblity (Gone) o algo así, XD luego haz algo así como

textview.setText (dataSnapshot.getValue (String.class))

luego, obténgalo con textview.getText (). toString ()

solo una loca idea simple.