Сохранение данных с помощью Firebase Realtime Database для C++

Начать

Если вы ещё не настроили приложение и доступ к базе данных, ознакомьтесь с руководством по Get Started.

Получение DatabaseReference

Чтобы записать данные в базу данных, вам нужен экземпляр DatabaseReference:

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

Сохранение данных

Записать данные в Firebase Realtime Database можно четырьмя способами:

Метод Примеры использования
SetValue() Запись или замена данных по определенному пути, например users/<user-id>/<username>.
PushChild() Добавьте в список данных. Каждый раз, когда вы вызываете Push(), Firebase генерирует уникальный ключ, который также можно использовать в качестве уникального идентификатора, например user-scores/<user-id>/<unique-score-id>.
UpdateChildren() Обновление некоторых ключей для определенного пути без замены всех данных.
RunTransaction() Обновление сложных данных, которые могут быть повреждены одновременными обновлениями.

Запись, обновление или удаление данных по ссылке

Основные операции записи

Для базовых операций записи можно использовать SetValue(), чтобы сохранить данные в указанной ссылке, заменив все существующие данные по этому пути. Этот метод позволяет передавать типы, поддерживаемые JSON, через тип Variant, который поддерживает:

  • Null (удаляет данные)
  • Целые числа (64-битные)
  • Числа с плавающей запятой двойной точности
  • Логические значения
  • Струны
  • Векторы вариантов
  • Сопоставление строк с вариантами

При таком использовании SetValue() перезаписывает данные в указанном месте, включая все дочерние узлы. Однако вы можете обновить дочерний узел, не перезаписывая весь объект. Если вы хотите разрешить пользователям обновлять свои профили, вы можете обновить имя пользователя следующим образом:

dbref.Child("users").Child(userId).Child("username").SetValue(name);

Добавление данных в список

Используйте метод PushChild(), чтобы добавить данные в список в многопользовательских приложениях. Метод PushChild() генерирует уникальный ключ каждый раз, когда в указанную ссылку Firebase добавляется новый дочерний элемент. Использование таких автоматически создаваемых ключей для каждого нового элемента в списке позволяет нескольким клиентам одновременно добавлять дочерние элементы в одно и то же место без конфликтов записи. Уникальный ключ, созданный PushChild(), основан на временной метке, поэтому элементы списка автоматически упорядочиваются в хронологическом порядке.

Вы можете использовать ссылку на новые данные, возвращенные методом PushChild(), чтобы получить значение автоматически сгенерированного ключа дочернего элемента или задать данные для него. Вызов GetKey() для ссылки PushChild() возвращает значение автоматически сгенерированного ключа.

Обновление определенных полей

Чтобы одновременно записать данные в определенные дочерние узлы, не перезаписывая другие, используйте метод UpdateChildren().

При вызове UpdateChildren() вы можете обновить дочерние значения более низкого уровня, указав путь к ключу. Если данные хранятся в нескольких местах для лучшего масштабирования, вы можете обновить все экземпляры этих данных, используя веерную рассылку данных. Например, в игре может быть класс LeaderboardEntry, который выглядит следующим образом:

class LeaderboardEntry {
  std::string uid;
  int score = 0;

 public:
  LeaderboardEntry() {
  }

  LeaderboardEntry(std::string uid, int score) {
    this->uid = uid;
    this->score = score;
  }

  std::map&ltstd::string, Object&gt ToMap() {
    std::map&ltstring, Variant&gt result = new std::map&ltstring, Variant&gt();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

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

void WriteNewScore(std::string userId, int score) {
  // Create new entry at /user-scores/$userid/$scoreid and at
  // /leaderboard/$scoreid simultaneously
  std::string key = dbref.Child("scores").PushChild().GetKey();
  LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
  std::map&ltstd::string, Variant&gt entryValues = entry.ToMap();

  std::map&ltstring, Variant&gt childUpdates = new std::map&ltstring, Variant&gt();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

В этом примере используется PushChild(), чтобы создать запись в узле, содержащем записи для всех пользователей в /scores/$key, и одновременно получить ключ с помощью key(). Затем этот ключ можно использовать для создания второй записи в баллах пользователя в /user-scores/$userid/$key.

Используя эти пути, вы можете одновременно обновлять несколько местоположений в дереве JSON с помощью одного вызова UpdateChildren(), как в этом примере, где создается новая запись в обоих местоположениях. Одновременные обновления, выполненные таким образом, являются атомарными: либо все обновления выполняются успешно, либо все обновления завершаются неудачно.

Удаление данных

Самый простой способ удалить данные – вызвать RemoveValue() для ссылки на местоположение этих данных.

Вы также можете удалить данные, указав null Variant в качестве значения для другой операции записи, например SetValue() или UpdateChildren(). Этот метод можно использовать с UpdateChildren(), чтобы удалить несколько дочерних элементов за один вызов API.

Узнайте, когда ваши данные будут зафиксированы.

Чтобы узнать, когда данные будут переданы на сервер Firebase Realtime Database, проверьте результат Future.

Сохранение данных в виде транзакций

При работе с данными, которые могут быть повреждены одновременными изменениями, например с инкрементными счетчиками, можно использовать транзакцию. Для этой операции вы задаете функцию DoTransaction. Эта функция принимает текущее состояние данных в качестве аргумента и возвращает новое желаемое состояние, которое вы хотите записать. Если другой клиент записывает данные в то же место до того, как ваше новое значение будет успешно записано, функция обновления вызывается снова с новым текущим значением, и запись повторяется.

Например, в игре можно разрешить пользователям обновлять таблицу лидеров с пятью лучшими результатами:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() &gt= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector&ltMutableData&gt children = mutableData.children();
      std::vector&ltMutableData&gt::iterator it;
      for (it = children.begin(); it != children.end(); ++it) {
        if (!it->value().is_map())
          continue;
        long childScore = (long)it->Child("score").value().int64_value();
        if (childScore &lt minScore) {
          minScore = childScore;
          minVal = &amp*it;
        }
      }
      if (minScore &gt score) {
        // The new score is lower than the existing 5 scores, abort.
        return kTransactionResultAbort;
      }

      // Remove the lowest score.
      children.Remove(minVal);
    }

    // Add the new high score.
    std::map&ltstd::string, Variant&gt newScoreMap =
      new std::map&ltstd::string, Variant&gt();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

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

Запись данных в офлайн-режиме

Если клиент потеряет подключение к сети, ваше приложение продолжит работать корректно.

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

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

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

Дальнейшие действия