Обновление Firestore только на сервере

# #firebase #google-cloud-firestore

Вопрос:

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

У меня есть определенное использование, когда узел управления и управления выдает команды в режиме реального времени удаленным узлам, поддерживаемым Firestore. Это требует, чтобы обновления выполнялись на сервере (или при сбое), чтобы узел Camp;C был уверен в выполнении (или сбое) в режиме реального времени. Что я хотел бы сделать, так это отключить использование кэша с этими обновлениями. Я не нашел способа сделать это. Возможно ли это при нынешних возможностях Firestore?

Обратите внимание, что нежелательно отключать кэширование Firestore на глобальном уровне, так как кэш полезен в других ситуациях.

—-РЕДАКТИРОВАТЬ——
На основе ответов я создал этот метод обновления, который пытается принудительно обновить сервер с помощью транзакции.

Пара заметок:

  • Это код дротика.
  • Utils.xyz-это внутренняя библиотека, и в данном случае она используется для ведения журнала.
  • Я снизил скорость сети для теста, чтобы имитировать плохое сетевое соединение.
  • Тайм — аут установлен на 5 секунд.

Вот вывод из моего журнала:
я/предсердий (22601): [2021-06-06 22:35:30] [осторожностью.Отладка] [FirestoreModel] [обновление] [мы находимся здесь!]
Я/предсердий (22601): [2021-06-06 22:35:47] [осторожностью.Отладка] [FirestoreModel] [обновление] [мы находимся здесь!]
Я/предсердий (22601): [2021-06-06 22:36:02] [осторожностью.Отладка] [FirestoreModel] [обновление] [мы находимся здесь!]
Я/предсердий (22601): [2021-06-06 22:37:18] [осторожностью.Отладка] [FirestoreModel] [обновление] [мы находимся здесь!]
Я/предсердий (22601): [2021-06-06 22:37:20] [осторожностью.ИНФОРМАЦИЯ] [Модель FirestoreModel] [обновление] [Транзакция завершена в 110929 мс.]

Firebase полностью игнорирует тайм-аут в 5 секунд; пытается обновить 4 раза каждый раз с интервалом ~15 секунд и, наконец, успешно работает через 110 секунд. Я получаю ответ в режиме реального времени в течение нескольких секунд (5 секунд) или сбой.

   Future<void> update(
    Map<String, dynamic> data, {
    WriteBatch batch,
    Transaction transaction,
    bool forceServer = false,
  }) async {
    // If updating there must be an id.
    assert(this.id != null);
    // Only one of batch or transaction can be non-null.
    assert(batch == null || transaction == null);
    // When forcing to update on server no transaction or batch is allowed.
    assert(!forceServer || (batch == null amp;amp; transaction == null));

    try {
      if (forceServer) {
        DateTime start = DateTime.now();
        await FirebaseFirestore.instance.runTransaction(
          (transaction) async {
            await update(data, transaction: transaction);
            Utils.logDebug('We are here!');
          },
          timeout: Duration(seconds: 5),
        );
        Utils.logDebug('Transaction successful in ${DateTime.now().difference(start).inMilliseconds}ms.');
      } else {
        DocumentReference ref =
            FirebaseFirestore.instance.collection(collection).doc(this.id);

        if (batch != null)
          batch.update(ref, data);
        else if (transaction != null)
          transaction.update(ref, data);
        else
          await ref.update(data);
      }
    } catch (e, s) {
      Utils.logException('Error updating document $id in $collection.', e, s);
      // Propagate the error.
      rethrow;
    }
  }
 

Комментарии:

1. Я не уверен, что полностью понимаю. Сначала вы говорите о слушателях, но затем, похоже, переключаетесь на написание обновлений. Можете ли вы более четко объяснить, какой это, или (еще лучше) показать код для операции, о которой вы спрашиваете?

2. Первый абзац-это утверждение, а второй-проблема. Проблема в том, что я не могу заставить обновление() находиться на сервере, в то время как опция принудительного чтения с сервера существует с помощью get(). Один из ответов предложил использовать транзакцию для достижения этой цели. Попробую это сделать и прокомментирую.

Ответ №1:

Для этого требуется, чтобы обновления выполнялись на сервере (или не выполнялись).

Для этого вы могли бы использовать Transactions and batched writes.

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

Комментарии:

1. Спасибо, это звучит так, как будто что-то может сработать. Я проверю это и доложу об этом.

2. Конечно, дай мне знать

3. Я попробовал ваше предложение. Пожалуйста, ознакомьтесь с отредактированным вопросом. При пакетной записи все еще может использоваться кэш. Согласно документации, сделка не проводится. Однако транзакция, похоже, не соблюдает тайм-аут и в одном тесте обновляет Firestore через 110 секунд. Неясно, полностью ли он избегает кэша. Это, конечно, не в режиме реального времени (быстро в течение нескольких секунд), который необходим для моего варианта использования.

Ответ №2:

Чтобы получить оперативные данные с сервера один раз, вы должны использовать:

 firebase.firestore()
  .doc("somecollection/docId")
  .get({ source: "server" })
  .then((snapshot) => {
    // if here, snapshot.data() is from the server
    // TODO: do something with data
  })
  .catch((err) => {
    // if here, get() encountered an error (insufficient permissions, server not available, etc)
    // TODO: handle the error
  });
 

Чтобы получать данные в реальном времени только с сервера (игнорируя кэш), вы бы использовали:

 const unsubscribe = firebase.firestore()
  .doc("somecollection/docId")
  .onSnapshot({ includeMetadataChanges: true }, {
    next(snapshot) {
      // ignore cache data
      if (snapshot.metadata.fromCache) return;

      // if here, snapshot.data() is from the server
      // TODO: do something with data
    },
    error(err) {
      // if here, onSnapshot() encountered an error (insufficient permissions, etc)
      // TODO: handle the error
    }
  });
 

Для записи на сервер вы должны использовать обычные операции записи — delete() , set() , и update() ;, поскольку все они возвращают обещания, которые не будут разрешены, пока клиент находится в автономном режиме. Если они разрешены, данные, хранящиеся на сервере, были обновлены.

Чтобы проверить, находитесь ли вы в Сети или нет, вы можете попробовать удалить несуществующий документ с сервера следующим образом:

 /**
 * Attempts to fetch the non-existant document `/.info/connected` to determine
 * if a connection to the server is available.
 * @return {Promise<boolean>} promise that resolves to a boolean indicating
 * whether a server connection is available
 */
function isCurrentlyOnline() {
  // unlike RTDB, this data doesn't exist and has no function
  // must be made readable in security rules
  return firebase.firestore()
    .doc(".info/connected")
    .get({ source: "server" })
    .then(
      () => {
        // read data successfully, we must be online
        return true;
      }, (err) => {
        // failed to read data, if code is unavailable, we are offline
        // for any other error, rethrow it
        if (err.code === "unavailable")
          return false;
        throw err;
      }
    );
}

/**
 * A function that attaches a listener to when a connection to Firestore has
 * been established or when is disconnected.
 *
 * This function listens to the non-existant `/.info/connected` document and
 * uses it's `fromCache` metadata to **estimate** whether a connection to
 * Firestore is currently available.

 * **Note:** This callback will only be invoked after the first successful
 * connection to Firestore
 *
 * @param {((error: unknown | null, isOnline: boolean) => unknown)} callback the
 * callback to invoke when the isOnline state changes
 * @return {(() => void)} a function that unsubscribes this listener when
 * invoked
 */
function onOnline(callback) {
  let hasConnected = false;
  // unlike RTDB, this data doesn't exist and has no function
  // must be made readable in security rules
  return firebase.firestore()
    .doc(".info/connected")
    .onSnapshot(
      { includeMetadataChanges: "server" },
      {
        next(snapshot) {
          const { fromCache } = snapshot.metadata;
          if (!hasConnected) {
            if (fromCache) return; // ignore this event

            hasConnected = true;
          }

          callback(null, !fromCache);
        },
        error(err) {
          callback(err);
        }
      }
    );
}