【问题标题】:How to wait for an async method to complete before the return statement runs? [duplicate]如何在返回语句运行之前等待异步方法完成? [复制]
【发布时间】:2021-02-05 23:23:10
【问题描述】:

我需要返回语句的 Firestore 查询结果。该方法返回一个空列表,因为 return 语句在任务完成之前运行。

这是我的 DatabaseManager 类中有问题的代码:

public List<Image> getImagesFromDatabase(Query query){

    List<Image> dataList= new ArrayList<Image>();
    query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                for (QueryDocumentSnapshot document : task.getResult()) {
                    Image image=document.toObject(Image.class);
                    image.setDocumentId(document.getId());
                    dataList.add(image);
                }
            } else {
                Log.d("DatabaseManager", "Error getting documents: ", task.getException());
            }
        }
    });
    return dataList;
}

这是使用先前方法返回的 List 的代码:

  private int getMaxNumberId(){
    Query query=dbManager.getCollectionReference().orderBy("numberId", Query.Direction.DESCENDING).limit(1);
    List<Image> images=dbManager.getImagesFromDatabase(query);
    Image maxImage=images.get(0);
    return maxImage.getNumberId();
}

我读过关于 Future 的文章,据我所知,这不是一个好的解决方案,以及关于制作回调接口的文章,但由于 return 语句,我不知道如何实现该解决方案。

【问题讨论】:

  • 请检查副本以了解如何解决该问题。那里有两个答案。第二个是使用 get() 和相应的回调。
  • @AlexMamo 谢谢,这行得通,但这是一个非常混乱的解决方案,至少对我来说,因为我有很多其他代码取决于 get() 的结果,因此,所有该代码必须在 onResponse() 中。
  • 另一种方法可能是使用 Kotlin 协程。

标签: java android firebase google-cloud-firestore


【解决方案1】:

您在使用此代码时面临的问题是它没有以同步方式运行。这基本上意味着它没有完全按照读取的方式执行。 getImagesFromDatabase() 正在做的是:

  1. 创建一个新的 ArrayList 并将其设置为您的 dataList 变量。
  2. 将您的查询发送到 Firebase(通过 get())并暂时忘记它。
  3. 继续您的代码,从而返回您的空dataList ArrayList。

这是一个异步实现,因为它在继续之前不会等待答案。因此,您需要一种方法让您的代码知道 Firebase 在执行 onComplete() 时返回了您的结果,因为现在它唯一要做的就是将您的结果设置为已经返回的过时实例。 在这种情况下,我可以想到 3 个解决方案。

  • 等待结果

    对于第一个解决方案,Firebase SDK 应该提供一个有用的方法来等待您的查询,换句话说,冻结调用它的线程(很可能是 UI 线程 - 所以不是一个好主意)。搜索了一段时间后,I see that Firebase SDK offers Tasks.await() 但它似乎给出了不同的结果,这不是推荐的方法,所以让我们继续下一个。

注意:根据this SO question,Firebase 甚至不提供同步实现,因此也排除了此选项。

  • 设置回调

    1. 创建一个接口来表示您想要的方法(即 onSuccess、onFailure)。
    2. 通过匿名类将所述接口实例化为变量并将该变量传递给您的getImagesFromDatabase() 方法。
    3. 在Firebase的onComplete()方法中调用变量的方法(即variable.onSuccess(results)
    4. 预计您在匿名类中的 onSuccess() 实现将被调用,您可能可以在结果中使用您需要的所有资源。

    有关此检查的更多详细信息,请查看 this SO answerthis one

  • 让你的变量Observable

    只要您在它们上设置了Observer,任何Observable 变量都会在发生更改时通知您。您可以使用 Android 的 .observe(lifecycleOwner, Observer{}) 方法或 by using DataBinding 如果您在项目中设置了它。这也符合 Android 的基于事件的编程、MVVM 架构的指南,并且通常会为您省去很多麻烦。

    您可以将您的 ArrayList 转换为 ObservableArrayList,将其实例化并按原样返回。然后你想在这个变量上设置一个监听器/观察器,最后在onComplete()中更新它的值。

考虑到您可能需要重构某些方法(例如getMaxNumberId()),以便以更具反应性的方式运行。特别是当您与 Firebase 通信时,您必须使用基于事件的方式,而不是具有常见功能返回的顺序方式(当然,除非它们是 Observables!)。

希望这会有所帮助,帕诺斯。

【讨论】:

    【解决方案2】:

    作为 Panos Gr,您可以使用 interface。首先你新建一个接口,同一个类也可以。

     private interface IQuery{
          void onSuccess(List<Image> imageList);
     }
    

    将新参数添加到方法getImagesFromDatabase

     public List<Image> getImagesFromDatabase(Query query, IQuery iQuery ){
            List<Image> dataList= new ArrayList<>();
            query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (task.isSuccessful()) {
                        for (QueryDocumentSnapshot document : task.getResult()) {
                            Image image=document.toObject(Image.class);
                            image.setDocumentId(document.getId());
                            dataList.add(image);
                            iQuery.onSuccess(dataList);
                        }
                    } else {
                        Log.d("DatabaseManager", "Error getting documents: ", task.getException());
                    }
                }
            });
            return dataList;
     }
    

    然后在方法getMaxNumberId调用接口

      private int getMaxNumberId(){
            Query query=dbManager.getCollectionReference().orderBy("numberId", Query.Direction.DESCENDING).limit(1);
            dbManager.getImagesFromDatabase(query, new IQuery() {
                @Override
                public void onSuccess(List<Image> imageList) {
                    Image maxImage=imageList.get(0);
                    return maxImage.getNumberId();
                }
            });
            Image maxImage=images.get(0);
            return maxImage.getNumberId();
      }
    

    希望你能理解

    【讨论】:

      猜你喜欢
      • 2020-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多