android firebase firebase-realtime-database

android - ¿Es posible cargar datos sincrónicamente desde Firebase?



firebase-realtime-database (6)

Estoy tratando de actualizar partes de un WebView en mi aplicación de Android con los datos que obtengo de un par conectado a través de Firebase. Para eso, podría ser útil ejecutar operaciones de bloqueo que devolverán los datos necesarios. Por ejemplo, una implementación del ejemplo de Chat que esperará hasta que otro participante del chat escriba algo antes de que push.setValue () regrese. ¿Es posible tal comportamiento con Firebase?


Aquí hay un ejemplo más largo basado en la respuesta compacta de Alex:

import com.google.android.gms.tasks.Tasks; import com.google.firebase.firestore.CollectionReference; import com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.Query; import com.google.firebase.firestore.QuerySnapshot;
final FirebaseFirestore firestore = FirebaseFirestore.getInstance(); final CollectionReference chatMessageReference = firestore.collection("chatmessages"); final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");
final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments(); final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);

Tenga en cuenta que esto no es una buena práctica ya que bloquea el hilo de la interfaz de usuario, en su lugar, se debe usar una devolución de llamada en general. Pero en este caso particular esto ayuda.


En una JVM normal, haría esto con primitivas de sincronización Java normales.

Por ejemplo:

// create a java.util.concurrent.Semaphore with 0 initial permits final Semaphore semaphore = new Semaphore(0); // attach a value listener to a Firebase reference ref.addValueEventListener(new ValueEventListener() { // onDataChange will execute when the current value loaded and whenever it changes @Override public void onDataChange(DataSnapshot dataSnapshot) { // TODO: do whatever you need to do with the dataSnapshot // tell the caller that we''re done semaphore.release(); } @Override public void onCancelled(FirebaseError firebaseError) { } }); // wait until the onDataChange callback has released the semaphore semaphore.acquire(); // send our response message ref.push().setValue("Oh really? Here is what I think of that");

Pero esto no funcionará en Android. Y eso es algo bueno, porque es una mala idea usar este tipo de enfoque de bloqueo en cualquier cosa que afecte la interfaz de usuario. La única razón por la que tenía este código por ahí es porque lo necesitaba en una prueba unitaria.

En el código real orientado al usuario, debe optar por un enfoque basado en eventos. Entonces, en lugar de "esperar a que lleguen los datos y luego enviar mi mensaje", yo diría "cuando entren los datos, envíe mi mensaje":

// attach a value listener to a Firebase reference ref.addValueEventListener(new ValueEventListener() { // onDataChange will execute when the current value loaded and whenever it changes @Override public void onDataChange(DataSnapshot dataSnapshot) { // TODO: do whatever you need to do with the dataSnapshot // send our response message ref.push().setValue("Oh really? Here is what I think of that!"); } @Override public void onCancelled(FirebaseError firebaseError) { } });

El resultado neto es exactamente el mismo, pero este código no requiere sincronización y no se bloquea en Android.


Hice una clase simple para llamar tareas sincrónicamente en Android.
Tenga en cuenta que esto es similar a la función de espera asíncrona de Javascript .
Comprueba mi gist .

Aquí hay un código de muestra para usarlo.

TasksManager.call(() -> { Tasks.await(AuthManager.signInAnonymously()); // You can use multiple Tasks.await method here. // Tasks.await(getUserTask()); // Tasks.await(getProfileTask()); // Tasks.await(moreAwesomeTask()); // ... startMainActivity(); return null; }).addOnFailureListener(e -> { Log.w(TAG, "signInAnonymously:ERROR", e); });


Se me ocurrió otra forma de obtener datos sincrónicamente. El requisito previo es no estar en el subproceso de interfaz de usuario.

final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>(); firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects(); tcs.setResult(mapper.map(dataSnapshot)); } @Override public void onCancelled(DatabaseError databaseError) { tcs.setException(databaseError.toException()); } }); Task<List<Object>> t = tcs.getTask(); try { Tasks.await(t); } catch (ExecutionException | InterruptedException e) { t = Tasks.forException(e); } if(t.isSuccessful()) { List<Object> result = t.getResult(); }

Probé mi solución y está funcionando bien, ¡pero por favor demuéstrame que estoy equivocado!


Si alguien también está pensando en cómo usar la corutina de Kotlin, puede usar kotlinx-coroutines-play-services .

Agregue a su aplicación el archivo build.gradle :

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1"

Entonces simplemente:

suspend fun signIn(email: String, password: String) { try { val auth: FirebaseAuth = FirebaseAuth.getInstance() auth.signInWithEmailAndPassword(email, password).await() } catch (e: FirebaseAuthException) { println("${e.errorCode}: ${e.message}") } }


import com.google.android.gms.tasks.Tasks; Tasks.await(taskFromFirebase);