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);