Почему onChildChanged() срабатывает перед onComplete() при использовании транзакций в Firebase?

В Firebase для Android я использую следующую структуру данных для чата с ограниченным количеством слотов:

/rooms
  /<roomid, generated by push()>
    /users
      one: null
      two: null
      three: null

Чтобы мои клиенты могли занять один из слотов (с one по three), я использую следующий код как указано здесь (версия JavaScript)< /а>:

var userid = "myuserid";
var ref = new Firebase("<my-firebase>.firebaseio.com/rooms/<roomid>/users");
ref.transaction(function(users) {
  if (!users.one) {
    // Claim slot 1
    users.one = userid;
    return users;
  } else if (!users.two) {
    // Claim slot 2
    users.two = userid;
    return users;
  } else if (!users.three) {
    // Claim slot 3
    users.three = userid;
    return users;
  }
  // Room is full, abort the transaction.
  return;
}, function(err, committed, snapshot) {
  if (committed && !err) {
    // Joined room successfully.
  } else {
    // Could not join room because it was full.
  }
});

После некоторого тестирования я обнаружил, что это не работает. Прав ли я со следующими двумя предположениями, объясняющими, почему это не работает?

  1. В runTransaction(...) (Android) или transaction(...) (JavaScript) необходимо установить необязательный параметр fireLocalEvents (Android) или applyLocally (JavaScript) в значение false, поскольку в противном случае вы будете получать события от транзакции, даже если она не будет успешной.
  2. Даже если для fireLocalEvents (Android) или applyLocally (JavaScript) задано значение false, события для onChildChanged(...) по этой ссылке будут срабатывать до успешной транзакции в onComplete(...). Это намеренное поведение?

В этом особом случае это означает, что я буду уведомлен о том, что комната чата заполнена (в ChildEventListener.onChildChanged(...)) прежде, чем я узнаю, что я сам получил последний слот (в Transaction.Handler.onComplete(...)).

Предположим, что если зал полон...

  • и у меня там слот, выполняется какой-то код и запускается что-то новое.
  • и ни один из слотов мне не достался, я просто хочу скрыть комнату из списка (поскольку она уже недоступна).

Если я хочу это сделать, я должен переместить всю логику для этого в ChildEventListener.onChildChanged(...), потому что там я сначала получу событие, верно? В противном случае комната будет скрыта до того, как я узнаю, что именно я получил там последний слот.

Любые идеи?


person caw    schedule 13.10.2014    source источник
comment
Кажется интуитивно понятным, что onchildchanged логически сработает до oncomplete.   -  person danny117    schedule 23.10.2014
comment
@danny117 Почему? Вы уведомлены сервером об изменении данных до того, как сервер фактически сообщит вам, что ваша транзакция, вызвавшая изменения, успешно завершена? Если бы я включил локальные события в обработчике транзакций, я бы согласился. Но как есть, не совсем. Вот для чего нужен флаг fireLocalEvents.   -  person caw    schedule 24.10.2014
comment
onComplete кажется полным?   -  person danny117    schedule 24.10.2014
comment
@ danny117 Так что да, данные не должны обновляться, пока транзакция не будет завершена. Кажется, вы согласны.   -  person caw    schedule 25.10.2014


Ответы (1)


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

Если вы когда-либо писали приложение, предоставляющее функции реального времени, то вы сталкивались с кучей спагетти из ошибок подключения и временных сбоев. Это приводит к множеству логики if/else/then для повторной попытки после восстановления соединения.

Firebase абстрагирует всю эту сложность, чтобы ваше приложение работало в автономном режиме так же, как и в сети. Вы по-прежнему можете прослушивать события, устанавливать/сохранять/обновлять данные и продолжать работу, как если бы они были в сети. При восстановлении соединения применяются события.

Точно так же, когда вы выполняете операцию set() локально, ожидание ответа сервера перед локальным применением изменений занимает очень много времени. Опять же, это приводит к большому количеству логики if/else для компенсации задержки — отслеживание внесения изменений локально и их немедленное отображение, чтобы пользователь не чувствовал, что приложение не отвечает.

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

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

person Kato    schedule 14.10.2014
comment
Спасибо! Я понял концепцию Firebase, но проблема в другом. Как видно из пронумерованного списка двух моих вопросов или предположений, я отключил локальные события для транзакции. По крайней мере, для этого должен быть параметр, верно? Проблема в том, что и локальное событие, и событие удаления запускаются до завершения транзакции. Таким образом, даже без локальных событий синхронизированное событие с сервера приходит до того, как я узнаю, что моя транзакция была успешно зафиксирована. Это, очевидно, не всегда может быть тем, что вы хотите. Это поведение задумано или ошибка? - person caw; 14.10.2014
comment
Привет, Марко, я не совсем понял вопрос здесь. Я оставлю это для контекста, хотя это не дает прямого ответа на вопрос. Я думаю, что мы видим, что ваше событие запускается после завершения транзакции, но до вашего обратного вызова onComplete. Интересно, как мы могли это проверить? - person Kato; 15.10.2014
comment
Что ж, легко воспроизвести транзакцию + прослушиватель дочерних событий. Когда вы сделаете это, вы увидите, что onChildChanged(...) всегда срабатывает до обратного вызова onComplete(...). Любой шанс, что это может быть изменено, если это так, как вы сказали, то есть если транзакция действительно завершается до того, как будет запущено дочернее событие. Я имею в виду, если это так, почему порядок обратных вызовов должен быть обратным? Или любая идея для обходного пути? - person caw; 15.10.2014