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

时间:2016-07-03 19:23:42

标签: android firebase firebase-realtime-database

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

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

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

我的Application类中的一些代码:

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)

2 个答案:

答案 0 :(得分:14)

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

我在Google I / O 2016上发表了关于任务的演讲。这是a link to a video,它直接跳转到该会话的相关部分。

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

}

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

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数据库查询,该查询不是数据库中节点的简单命名引用。这取决于它们是否以对键入HashMap的方式进行哈希处理。

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

答案 1 :(得分:5)

我的应用程序遇到了同样的问题。经过长时间研究如何检查firebase是否完成检索数据,我找到了这个解决方案。

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

因此,使用onDataChanged()并在其angular.module('stackOverflowExample', []).controller('ExampleCtrl', function($scope){ $scope.myObjectKeysArray = [ 'sdav', 'arfg', 'wefc' ]; $scope.myObject = { sdav: {name:'mark', age:23}, arfg: {name:'lucy', age:18}, wefc: {name:'kat', age:19}, } })内执行从Firebase检索数据后要执行的内容。

这对我有用,希望它也适合你