Загружать макет только после завершения вызовов firebase

Проблема: макет в MainActivity генерируется до того, как у меня появится возможность завершить вызов firebase для восстановления данных приложения. Если я поверну экран, что приведет к повторному запуску onCreate в MainActivity, все сгенерируется просто отлично.

В моем приложении есть пользовательская реализация класса Application, которая делает несколько вызовов Firebase, чтобы восстановить данные / убедиться, что данные всегда синхронизированы. Однако вместо нескольких ValueEventListeners с кучей дочерних элементов у меня есть около 20 ValueEventListeners. Это сделано для того, чтобы мое приложение не синхронизировало почти всю базу данных каждый раз, когда пользователь генерирует крошечные данные, и избегает конфликтов, которые могут возникнуть при асинхронной обработке данных. Интересно, что 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 ответа

В настоящее время я использую служебный класс, который я создал для запуска нескольких загрузок данных. Это вызовет финальную задачу, когда все слушатели будут готовы. Ключевым моментом здесь является умное использование нового средства Task для асинхронного программирования, предоставляемого Play Services. Вероятно, вы могли бы сделать все это, используя какую-то другую асинхронную среду, но задачи поставляются бесплатно с сервисами Play и используются в других частях 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());
        }
    }

}

То, как вы используете это, вот так. Определить все DatabaseReferences вам нужно загрузить данные и сохранить их в элементах вашей деятельности. Затем во время вашей деятельности onStart(), передайте их все в экземпляр FirebaseMultiQuery и вызовите start() в теме. Он вернет Задачу, которая генерирует Карту DatabaseReference к 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 Storage использует Задачи, как и Firebase Authentication.

Приведенный здесь код может в настоящее время не работать с запросом к базе данных Firebase, который не является простой именованной ссылкой на узел в вашей базе данных. Это зависит от того, хэшированы ли они способом, который является последовательным для ввода в HashMap.

Приведенный выше код все еще несколько экспериментальный, но у меня до сих пор не было проблем с ним. Вроде бы много кода, но если вы выполняете много одновременной загрузки, это действительно удобно.

Я столкнулся с той же проблемой с моим приложением. После долгих исследований о том, как проверить, была ли запущена база данных для извлечения данных, я нашел это решение.

Поэтому, когда у вас есть несколько слушателей событий в вашем приложении. Firebase не выполняет эти слушатели по очереди.
Слушатель события значения всегда выполняется в конце.
Таким образом, если вы хотите выполнить что-то, когда все данные извлечены, вы можете вызвать ValueEventListener и выполнить метод, который загружает все ваши представления внутри него.

Так что используйте rootRef.addListenerForSingleValueEvent() и внутри его onDataChanged() выполнить то, что вы хотите выполнить после получения данных из Firebase.

Это сработало для меня, надеюсь, это работает и для вас

@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()) { ...}
Другие вопросы по тегам