当前位置: 首页 > 知识库问答 >
问题:

仅在firebase调用完成时加载布局

任伟
2023-03-14

问题是:我的MainActivity中的布局是在我有机会完成调用firebase以恢复应用程序数据之前生成的。如果我旋转屏幕,从而导致onCreate在MainActivity中再次运行,则生成的所有内容都正常。

在我的应用程序中,我有一个自定义的应用程序类实现,它对Firebase进行一系列调用,以恢复数据/确保数据始终同步。然而,我有大约20个ValueEventListeners,而不是几个下面有很多孩子的ValueEventListeners。这是为了防止每次用户生成少量数据时,我的应用程序几乎同步整个数据库,并避免在异步操作数据时可能发生的冲突。有趣的是,ValueEventListeners实际上并没有按照编码的顺序获取数据,所以当最后一个完成时,我无法将布尔值设置为true。

我想知道是否有一种简单的方法来检测Firebase读取是否全部完成,而不是在每个监听器的末尾放置一些代码并执行手动执行此操作。我查看了Application类和Firebase中可用的方法,到目前为止我还没有找到任何有效的方法。

我的应用程序类中的一些代码:

public class CompassApp extends Application {

...然后在应用程序的onCreate中:

//        Fetching Data from DB.
    FirebaseDatabase database = FirebaseDatabase.getInstance();
    DatabaseReference dbRef = database.getReference();

//        Current User Data
    dbRef.child("currentAppData").child("workingOut").addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            activeFirebaseConnections += 1;
//                Stops executing method if there is no data to retrieve
            if (!dataSnapshot.exists()) {
                return;
            }

            workingOut = dataSnapshot.getValue(boolean.class);

            activeFirebaseConnections -= 1;

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Log.e(TAG, "Firebase read of sleepDebt failed");
        }
    });
    dbRef.child("currentAppData").child("sleeping").addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            activeFirebaseConnections += 1;
//                Stops executing method if there is no data to retrieve
            if (!dataSnapshot.exists()) {
                return;
            }

            sleeping = dataSnapshot.getValue(boolean.class);

            activeFirebaseConnections -= 1;

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Log.e(TAG, "Firebase read of sleepDebt failed");
        }
    });

(等等...剩下的只是更多的ValueEventListeners)

共有3个答案

羊柏
2023-03-14

@Doug Stevenson回答很棒。对我来说几乎是现成的。我唯一改变的是,我没有显式创建AllOnCompleteListener类,而是编写了如下代码:

    private OnCompleteListener<Map<DatabaseReference, DataSnapshot>> allListener;...



allListener = new OnCompleteListener<Map<DatabaseReference, DataSnapshot>>() {
      @Override
      public void onComplete(@NonNull Task<Map<DatabaseReference, DataSnapshot>> task) {
          if (task.isSuccessful()) {
          final Map<DatabaseReference, DataSnapshot> result = task.getResult();
          // Look up DataSnapshot objects using the same DatabaseReferences you passed into FirebaseMultiQuery



          for (Map.Entry<DatabaseReference, DataSnapshot> entry : result.entrySet()) { ...}
彭令秋
2023-03-14

我的应用程序也面临着同样的问题。在对如何检查firebase是否完成了数据检索进行了长时间的研究后,我找到了这个解决方案。< br>

因此,当您的应用程序中有多个事件侦听器时。Firebase不按顺序逐个执行此侦听器
值事件侦听器始终在最后执行
因此,如果要在检索到所有数据时执行某些操作,可以调用ValueEventListener并执行一个方法,该方法将加载其中的所有视图。

因此,使用 rootRef.addListenerForSingleValueEvent() 并在其 onDataChanged() 中执行从 Firebase 检索数据后要执行的内容。

这对我有用,希望对你也有用

蓬琦
2023-03-14

我目前正在使用我为启动多个数据负载而创建的实用程序类。当所有侦听器都完成时,它将触发最终任务。这里的关键是巧妙地使用Play Services提供的新任务工具进行异步编程。您可能可以使用其他异步框架来完成所有这些,但任务随Play Services免费提供,并在Firebase的其他部分中使用,因此您不妨学习使用它们。:-)

我在 Google I/O 2016 上做了一个关于任务的演讲。下面是一个视频链接,该视频直接跳转到该会话的相关部分。

public class FirebaseMultiQuery {

    private final HashSet<DatabaseReference> refs = new HashSet<>();
    private final HashMap<DatabaseReference, DataSnapshot> snaps = new HashMap<>();
    private final HashMap<DatabaseReference, ValueEventListener> listeners = new HashMap<>();

    public FirebaseMultiQuery(final DatabaseReference... refs) {
        for (final DatabaseReference ref : refs) {
            add(ref);
        }
    }

    public void add(final DatabaseReference ref) {
        refs.add(ref);
    }

    public Task<Map<DatabaseReference, DataSnapshot>> start() {
        // Create a Task<DataSnapsot> to trigger in response to each database listener.
        //
        final ArrayList<Task<DataSnapshot>> tasks = new ArrayList<>(refs.size());
        for (final DatabaseReference ref : refs) {
            final TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
            final ValueEventListener listener = new MyValueEventListener(ref, source);
            ref.addListenerForSingleValueEvent(listener);
            listeners.put(ref, listener);
            tasks.add(source.getTask());
        }

        // Return a single Task that triggers when all queries are complete.  It contains
        // a map of all original DatabaseReferences originally given here to their resulting
        // DataSnapshot.
        //
        return Tasks.whenAll(tasks).continueWith(new Continuation<Void, Map<DatabaseReference, DataSnapshot>>() {
            @Override
            public Map<DatabaseReference, DataSnapshot> then(@NonNull Task<Void> task) throws Exception {
                task.getResult();
                return new HashMap<>(snaps);
            }
        });
    }

    public void stop() {
        for (final Map.Entry<DatabaseReference, ValueEventListener> entry : listeners.entrySet()) {
            entry.getKey().removeEventListener(entry.getValue());
        }
        snaps.clear();
        listeners.clear();
    }

    private class MyValueEventListener implements ValueEventListener {
        private final DatabaseReference ref;
        private final TaskCompletionSource<DataSnapshot> taskSource;

        public MyValueEventListener(DatabaseReference ref, TaskCompletionSource<DataSnapshot> taskSource) {
            this.ref = ref;
            this.taskSource = taskSource;
        }

        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            snaps.put(ref, dataSnapshot);
            taskSource.setResult(dataSnapshot);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            taskSource.setException(databaseError.toException());
        }
    }

}

你使用它的方式是这样的。确定需要从中加载数据的所有数据库引用,并将其存储在活动的成员中。然后,在 onStart() 上的活动期间,将它们全部传递到 FirebaseMultiQuery 的实例中,并在其上调用 start()。它将返回一个任务,该任务将生成数据库引用的映射到它将生成的DataSnapshot。掌握该任务后,注册一个最终侦听器,该侦听器将在加载所有数据时触发:

firebaseMultiQuery = new FirebaseMultiQuery(dbRef1, dbRef2, dbRef3, ...);
final Task<Map<DatabaseReference, DataSnapshot>> allLoad = firebaseMultiQuery.start();
allLoad.addOnCompleteListener(activity, new AllOnCompleteListener());

在你的 onStop() 中,不要忘记调用 stop() 以确保一切都已正确关闭:

firebaseMultiQuery.stop();

任务完成后触发的侦听器接收映射中的所有数据,其外观如下:

private class AllOnCompleteListener implements OnCompleteListener<Map<DatabaseReference, DataSnapshot>> {
    @Override
    public void onComplete(@NonNull Task<Map<DatabaseReference, DataSnapshot>> task) {
        if (task.isSuccessful()) {
            final Map<DatabaseReference, DataSnapshot> result = task.getResult();
            // Look up DataSnapshot objects using the same DatabaseReferences you passed into FirebaseMultiQuery
        }
        else {
            exception = task.getException();
            // log the error or whatever you need to do
        }
        // Do stuff with views
        updateUi();
    }
}

也许值得注意的是,写入数据的Firebase数据库调用也会返回您可以侦听以完成的任务。此外,Firebase存储使用任务,Firebase身份验证也是如此。

此处的代码当前可能不适用于 Firebase 数据库查询,该查询不是对数据库中节点的简单命名引用。这取决于它们是否以一种与键入哈希映射一致的方式进行哈希处理。

上面的代码仍然有些实验性,但到目前为止我还没有遇到任何问题。这看起来有很多代码,但是如果你做很多并发加载,它真的很方便。

 类似资料:
  • 如何在包含回收器视图的活动开始时显示Progressbar,一旦回收器视图从firebase数据库加载数据,应该隐藏该视图? 在onCreate方法中,我显示了我的ProgressBar,但我不知道什么时候应该隐藏它。 你们能给我点主意吗?谢谢

  • 我是胡克斯的新手,遇到了一些让我追自己尾巴的案例。 希望有人能解释或提供对我有意义的解决方案: > 下面这个线程:如何调用加载函数与反应使用效果只有一次 我试着在没有依赖性的情况下使用useEffect,eslint不喜欢这样,他们建议添加一个“跳过下一行”,这似乎有点骇人:

  • 问题内容: 我正在将React与Firebase一起使用来开发一个小型Web应用程序。为了进行身份验证,我使用了上下文API,并且在上下文中添加了登录用户的详细信息。 AuthProvider.tsx AuthConsumer.tsx PrivateRoute.tsx App.tsx 用户已经登录,并且会话持久性设置为本地。问题是,当我尝试本地主机/订阅(这是一条私有路由)时,context.is

  • 我正在应用程序中从firebase realtime下载数据。我们在splashscreen上下载这些数据。在下载此数据之前,splashscreen屏幕不应关闭。找不到如何执行此操作 这是我的密码;

  • PrimeFaces 5.0、JSF 2.2、Glassfish 4.1.1、, 我假设我的设置中有些东西没有正确配置,但我不确定该看什么。。。 所以我使用managedbeans来支持JSF页面。在页面上的PrimeFace元素中,如果我使用oncomplete属性,它引用的方法将在页面加载时被调用。我不想让它在每次加载页面时调用这个方法,我也不认为应该这样! 我已经在几页和不同的元素中对此进行

  • 问题内容: 浏览器窗口调整大小后如何调用函数? 我正在尝试这样做,但是遇到了问题。我正在使用JQuery Resize事件函数: 但是,如果用户手动调整浏览器窗口的大小,则会 连续 调用此功能。这意味着,它可能会在很短的时间间隔内多次调用此函数。 如何仅 一次 调用调整大小功能(一旦浏览器窗口调整大小后)? 更新 也不必使用全局变量。 问题答案: 您可以将参考ID存储到任何setInterval或