【问题标题】:Is it possible to synchronously load data from Firebase?是否可以从 Firebase 同步加载数据?
【发布时间】:2015-10-20 10:57:21
【问题描述】:

我正在尝试使用从通过 Firebase 连接的对等方获取的数据更新我的 Android 应用程序中的部分 WebView。为此,执行将返回所需数据的阻塞操作可能会有所帮助。例如,Chat 示例的一个实现将等到另一个聊天参与者在 push.setValue() 返回之前写了一些东西。 Firebase 可以实现这种行为吗?

【问题讨论】:

    标签: android firebase firebase-realtime-database


    【解决方案1】:
    import com.google.android.gms.tasks.Tasks;
    
    Tasks.await(taskFromFirebase);
    

    【讨论】:

    • 非常感谢,我也在找这个;但这如何解决“执行将返回所需数据的阻塞操作”?事件监听器不是任务,那你怎么做呢?
    • 这个以阻塞方式从firebase执行任务,你要么得到响应,要么抛出错误。所以你不需要任何回调
    • 听起来不错,更详细一点,链接和背景在我投票之前会很好:)
    • Firebase 团队现已弃用 Tasks,并建议我们使用 ApiFutures:googleapis.github.io/api-common-java/1.1.0/apidocs/com/google/…
    【解决方案2】:

    在常规 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。这是一件好事,因为在影响用户界面的任何事情中使用这种类型的阻塞方法是一个坏主意。我有这段代码的唯一原因是因为我需要进行单元测试。

    在真正面向用户的代码中,您应该采用事件驱动的方法。因此,我不会“等待数据到来,然后发送我的消息”,而是“当数据进来时,发送我的消息”:

    // 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) {
            throw firebaseError.toException();
        }
    });
    

    最终结果完全一样,但这段代码不需要同步,在 Android 上也不会阻塞。

    【讨论】:

    • 谢谢弗兰克!我同意阻塞与事件驱动的方法。但是,我必须对 WebView 执行多次更新,就像 WebView.loadDataWithBaseURL() 一样,HTTP 请求(WebView 执行)也被阻塞。我必须这样做而不是 WebView 本身的原因是服务器无法访问应用程序,我将 Firebase 用作“隧道”。任何更好的解决方案将不胜感激。另外,如果阻塞部分位于每次 WebView 请求更新时可能触发的事件中,信号量解决方案是否会失败?
    • 我很确定上面的 sn-p 解决了您提供的聊天示例。如果您的用例实际上不同,请提供stackoverflow.com/help/mcve。代码在一周中的每一天都胜过对该代码的描述。
    • @Dani 它是如何工作的?因为acquire会阻塞主线程并且release永远不会被执行,因为它也应该在主线程上运行?
    • @Frank:您使用信号量的示例可能适用于早期版本的 Firebase,但不适用于 9.0.2。我尝试过这个。侦听器回调在主线程上运行,主线程被阻塞等待获取信号量。当我使用semaphore.tryAcquire(10, TimeUnit.SECONDS) 时,它总是超时然后触发回调。
    • 存在问题,ios提供了很好的集中线程机制。使用 Parse api,您可以通过调用同步 api 来使用它们。 Firebase 很烂
    【解决方案3】:

    我想出了另一种同步获取数据的方法。 先决条件是不在 UI 线程上。

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

    我测试了我的解决方案,它运行良好,但请证明我错了!

    【讨论】:

    • 这个新的 SnapshotToObjects() 是什么;它是任何内置类还是您创建了自己的类。如果它是你自己的类,你能告诉它和它的参数吗
    • 它是我实现的,只是DataSnapshot -> List的映射器。
    • 这个例子现在有了真正的实现。在我的例子中,它将 DataSnapshot 转换为食谱列表。你可以找到一个例子here
    【解决方案4】:

    这是一个基于 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);

    请注意,这是不是好的做法,因为它会阻塞 UI 线程,通常应该使用回调。但在这种特殊情况下,这会有所帮助。

    【讨论】:

      【解决方案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 的异步等待功能。
        检查我的gist

        这是使用它的示例代码。

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

        【讨论】:

          猜你喜欢
          • 2021-01-03
          • 1970-01-01
          • 2017-03-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-14
          • 2022-09-23
          相关资源
          最近更新 更多