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

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

问题描述

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

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



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



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

  public class CompassApp extends Application {

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

  //从DB获取数据。 
FirebaseDatabase数据库= FirebaseDatabase.getInstance();
DatabaseReference dbRef = database.getReference();
$ b $ //当前用户数据
dbRef.child(currentAppData)。child(workingOut)。addValueEventListener(new ValueEventListener(){
@Override
public void onDataChange(DataSnapshot dataSnapshot){
activeFirebaseConnections + = 1;
//如果没有数据检索
,则停止执行方法if(!dataSnapshot.exists()){
返回;
}

workingOut = dataSnapshot.getValue(boolean.class);

activeFirebaseConnections - = 1;

}
$ b $ @覆盖
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;
//如果没有数据检索
,则停止执行方法if(!dataSnapshot.exists()){
return;
}

sleeping = dataSnapshot.getValue(boolean.class);

activeFirebaseConnections - = 1;

}

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

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

解决方案

我正在使用一个实用工具类来创建多个负载的数据。当所有听众完成后,它将触发最后的任务。他们在这里关键是巧妙地使用新的任务由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<>();
$ b $ public FirebaseMultiQuery(final DatabaseReference ... refs){
for(final DatabaseReference ref:refs){
add(ref);


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

public Task< Map< DatabaseReference,DataSnapshot>> start(){
//创建一个任务< DataSnapsot>来响应每个数据库监听器来触发。
//
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());
}

//返回所有查询完成时触发的单个Task。它包含
//原始给出的所有原始数据库引用映射到它们的结果
// 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);
}
} ); (最终Map.Entry< DatabaseReference,ValueEventListener>条目:listeners.entrySet()){
entry.getKey(){
}

public void stop ().removeEventListener(entry.getValue());
}
snaps.clear();
listeners.clear();


private class MyValueEventListener实现了ValueEventListener {
private final DatabaseReference ref;
private final TaskCompletionSource< DataSnapshot> taskSource;
$ b $ public MyValueEventListener(DatabaseReference ref,TaskCompletionSource&DataSnapshot> taskSource){
this.ref = ref;
this.taskSource = taskSource;

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

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



$ b code
$ b $ p
$ b

你用它就是这样。确定所有的 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实现OnCompleteListener< Map< DatabaseReference,DataSnapshot>> {
@Override
public void onComplete(@NonNull Task< Map&DatabaseReference,DataSnapshot>> task){
if(task.isSuccessful()){
final Map< DatabaseReference,DataSnapshot> result = task.getResult();
//使用传递给FirebaseMultiQuery的相同DatabaseReferences查找DataSnapshot对象

else {
exception = task.getException();
//记录错误或任何你需要做的事情

//做视图
updateUi();




$ b $ p
$ b

Firebase数据库可能会调用写数据也返回任务,你可以听完成。此外,Firebase存储使用任务,Firebase身份验证也是如此。



此处的代码可能不适用于Firebase数据库查询,该查询不是对节点的简单命名引用在你的数据库中。这取决于它们是否以一致的方式被哈希映射成一个HashMap。



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

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.

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.

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 {

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

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

解决方案

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. :-)

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

}

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

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

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.

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天全站免登陆