Можно ли синхронно загружать данные из Firebase?

Я пытаюсь обновить части WebView в своем приложении для Android с данными, которые я получаю от однорангового узла, подключенного через Firebase. Для этого было бы полезно выполнить блокирующие операции, которые возвратят необходимые данные. Например, реализация примера чата, который будет ждать, пока другой участник чата ничего не напишет перед возвратом push.setValue(). Возможно ли такое поведение с Firebase?

Ответ 1

На регулярной JVM вы будете делать это с помощью обычных примитивов синхронизации Java.

Например:

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

Но это не будет работать на Android. И это хорошая вещь, потому что это плохая идея использовать этот тип блокирующего подхода во всем, что влияет на пользовательский интерфейс. Единственная причина, по которой я лежала в этом коде, - это то, что мне нужно было в unit test.

В реальном коде, ориентированном на пользователя, вы должны пойти на подход, основанный на событиях. Поэтому вместо "дождитесь появления данных и затем отправить мое сообщение", я бы "когда данные вошли, отправьте мое сообщение":

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

    }
});

Конечный результат точно такой же, но этот код не требует синхронизации и не блокируется на Android.

Ответ 2

import com.google.android.gms.tasks.Tasks;

Tasks.await(taskFromFirebase);

Ответ 3

Я придумал другой способ получения данных синхронно. Предварительное условие не должно быть в теме пользовательского интерфейса.

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

Я тестировал свое решение, и он работает нормально, но, пожалуйста, подтвердите, что я ошибаюсь!

Ответ 4

Вот более длинный пример, основанный на компактном ответе Алекса:

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

Обратите внимание, что это не очень хорошая практика, так как он блокирует поток пользовательского интерфейса, вместо него следует использовать обратный вызов. Но в данном конкретном случае это помогает.

Ответ 5

Если кто-то также думает о том, как использовать сопрограмму Kotlin, вы можете воспользоваться kotlinx-coroutines-play-services.

Добавьте в свое приложение файл build.gradle:

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

Тогда просто:

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

Ответ 6

Я сделал простой класс для синхронного вызова задач в Android.
Обратите внимание, что это похоже на функцию асинхронного ожидания Javascript.
Проверьте мою суть.

TasksManager.class

public class TasksManager {

    ...

    public ExecutorService getExecutor() {
        if (mDefaultExecutor == null || mDefaultExecutor.isShutdown() || mDefaultExecutor.isTerminated()) {
            // Create a new ThreadPoolExecutor with 2 threads for each processor on the
            // device and a 60 second keep-alive time.
            int numCores = Runtime.getRuntime().availableProcessors();
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    numCores * 2,
                    numCores * 2,
                    60L,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>()
            );
            mDefaultExecutor = executor;
        }
        return mDefaultExecutor;
    }

    public static <TResult> Task<TResult> call(@NonNull Callable<TResult> callable) {
        return Tasks.call(getInstance().getExecutor(), callable);
    }
}

Вот пример кода для его использования.

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

Ответ 7

Я сделал простой класс для синхронного вызова задач в Android.
Обратите внимание, что это похоже на функцию асинхронного ожидания Javascript.
Проверьте мою суть.

Вот пример кода для его использования.

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