仅在 firebase 调用完成时加载布局 [英] Only load layout when firebase calls are complete

查看:26
本文介绍了仅在 firebase 调用完成时加载布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The problem: the layout in my MainActivity is generated before I have a chance to finish calling the firebase to restore app data. If I rotate the screen, thus causing onCreate to run again in the MainActivity, everything is generated just fine.

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

In my app, I have a custom implementation of the Application class, which makes a bunch of calls to Firebase in order to restore data / make sure data is always in sync. However, instead of having just a few ValueEventListeners with a ton of children underneath, I have around 20 ValueEventListeners. This is to prevent my app from syncing nearly the entire database every time the user generates a tiny bit of data, and to avoid conflicts that can happen when data is manipulated asynchronously. Interestingly, the ValueEventListeners don't actually fetch their data in the order that they're coded in, so I can't set a bool to true when the last one is done.

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

I'm wondering if there's a simple way to detect whether the firebase reads are all done, other than placing some code at the end of every single Listener and performing an operation that does this manually. I've looked at the methods available in the Application class, as well as Firebase, and so far I haven't found anything that works.

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

some code from my Application class:

public class CompassApp extends Application {

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

... then inside the Application's 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)

(and so on... the rest is just more ValueEventListeners)

推荐答案

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

I'm currently using a utility class that I created for kicking off multiple loads of data. It will trigger a final Task when all of the listeners are complete. They key here is clever use of the new Task facility for asynchronous programming provided by Play Services. You could probably do all this using some other async framework, but Tasks come for free with Play Services and are used in other parts of Firebase, so you may as well learn to use them. :-)

我在 2016 年 Google I/O 大会上发表了关于任务的演讲.这是视频链接,可直接跳转到该会话的相关部分.

I gave a talk at Google I/O 2016 about tasks. Here is a link to a video that jumps directly to the relevant part of that session.

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

}

你使用它的方式是这样的.确定您需要从中加载数据并将它们存储在您的 Activity 成员中的所有 DatabaseReference.然后,在您的活动 onStart() 期间,将它们全部传递到 FirebaseMultiQuery 的实例中,并对其调用 start().它将返回一个 Task,该 Task 生成一个 Map of DatabaseReference 到它将生成的 DataSnapshot.有了这个任务,注册一个最终的监听器,它会在所有数据加载时触发:

The way you use it is like this. Determine all the DatabaseReferences you need to load data from and store them in members of your Activity. Then, during your activity onStart(), pass them all into an instance of FirebaseMultiQuery and call start() on it. It will return a Task that generates a Map of DatabaseReference to the DataSnapshot it will generate. With that Task in hand, register a final listener that will trigger when all the data is loaded:

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

并且在您的 onStop() 中,不要忘记在其上调用 stop() 以确保一切正常关闭:

And in your onStop(), don't forget to call stop() on it to make sure everything is shut down properly:

firebaseMultiQuery.stop();

由任务完成触发的接收地图中所有数据的侦听器可能如下所示:

The listener triggered by the completion of the task that receives all the data in a map can look something like this:

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 身份验证也是如此.

It's perhaps worth noting that Firebase Database calls that write data also return Tasks that you can listen on for completion. Also, Firebase Storage uses Tasks, and so does Firebase Authentication.

此处的代码目前可能不适用于 Firebase 数据库查询,该查询不是对数据库中节点的简单命名引用.这取决于它们是否以与键入 HashMap 的方式一致的方式散列.

The code here may not currently work with a Firebase Database Query that is not a simple named reference into a node in your database. It depends on whether they are hashed in a way that's consistent for keying into a HashMap.

上面的代码仍处于实验阶段,但到目前为止我还没有遇到任何问题.看起来代码很多,但是如果你做很多并发加载,真的很方便.

The code above is still somewhat experimental, but I haven't had any problems with it so far. It seems like a lot of code, but if you do a lot of concurrent loading, it's really handy.

这篇关于仅在 firebase 调用完成时加载布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆