Guarda datos con Firebase Realtime Database para C++

Comenzar

Si aún no has configurado la app ni el acceso a la base de datos, primero consulta la guía Get Started.

Obtén una DatabaseReference

Para escribir en la base de datos, necesitas una instancia de DatabaseReference:

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

Guarda datos

Existen cuatro métodos para escribir datos en Firebase Realtime Database:

Método Usos comunes
SetValue() Escribir o reemplazar datos en una ruta de acceso definida, como users/<user-id>/<username>
PushChild() Agregar datos a una lista de datos. Cada vez que llamas a Push(), Firebase genera una clave única que también se puede usar como identificador único, por ejemplo user-scores/<user-id>/<unique-score-id>.
UpdateChildren() Actualizar algunas de las claves de una ruta de acceso definida sin reemplazar todos los datos.
RunTransaction() Actualizar datos complejos que pueden dañarse con actualizaciones simultáneas.

Escribe, actualiza o borra datos de una referencia

Operaciones básicas de escritura

Si quieres ejecutar operaciones básicas de escritura, puedes usar SetValue() para guardar datos en una referencia específica y reemplazar todos los datos de esa ruta de acceso. Puedes usar este método para pasar tipos aceptados por JSON a través de un tipo de variante compatible con lo siguiente:

  • Null (esto borra los datos)
  • Enteros (64 bits)
  • Números de punto flotante con precisión doble
  • Booleanos
  • Strings
  • Vectores de variantes
  • Mapas de strings a variantes

Si usas SetValue() de esta forma, se reemplazan los datos en la ubicación especificada, incluidos los nodos secundarios. Sin embargo, es posible actualizar un elemento secundario sin volver a escribir el objeto entero. Si deseas permitir que los usuarios actualicen sus perfiles, podrías actualizar el nombre de usuario de la siguiente forma:

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

Agrega datos a una lista de datos

Usa el método PushChild() para agregar datos a una lista en aplicaciones multiusuario. Cada vez que se agrega un elemento secundario nuevo a la referencia de Firebase especificada, el método PushChild() genera una clave única. Cuando se usan estas claves generadas de forma automática para cada elemento nuevo de la lista, varios clientes pueden agregar elementos secundarios a la misma ubicación, al mismo tiempo y sin conflictos de escritura. La clave única que genera PushChild() se basa en una marca de tiempo. Por lo tanto, los elementos de las listas se ordenan cronológicamente de forma automática.

Puedes usar la referencia a los datos nuevos que muestra el método PushChild() para obtener el valor de la clave generada automáticamente del elemento secundario o configurar los datos de dicho elemento. Si llamas a GetKey() en una referencia PushChild(), se muestra la clave generada de forma automática.

Actualiza campos específicos

Para escribir de forma simultánea en elementos secundarios específicos de un nodo sin sobrescribir otros nodos secundarios, usa el método UpdateChildren().

Cuando llamas a UpdateChildren(), puedes especificar una ruta de acceso de la clave para actualizar valores secundarios de nivel inferior. Si se almacenan datos en varias ubicaciones para obtener un mejor escalamiento, puedes actualizar todas las instancias de esos datos mediante fan-out de datos. Por ejemplo, un juego puede tener una clase LeaderboardEntry como la siguiente:

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

 public:
  LeaderboardEntry() {
  }

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

  std::map<std::string, Object> ToMap() {
    std::map<string, Variant> result = new std::map<string, Variant>();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

Para crear una LeaderboardEntry y actualizarla de forma simultánea en el feed de puntuación reciente y en la lista de puntuaciones propia del usuario, el juego usa el siguiente código:

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<std::string, Variant> entryValues = entry.ToMap();

  std::map<string, Variant> childUpdates = new std::map<string, Variant>();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

En este ejemplo, se usa PushChild() para crear una entrada en el nodo que contiene las entradas de todos los usuarios en /scores/$key y recuperar la clave con key() de manera simultánea. Luego, la clave puede usarse para crear una segunda entrada en las puntuaciones del usuario en /user-scores/$userid/$key.

Con estas rutas de acceso, puedes ejecutar actualizaciones simultáneas en varias ubicaciones del árbol JSON con una única llamada a UpdateChildren(), de manera similar a este ejemplo en el que se crea la entrada nueva en ambas ubicaciones. Las actualizaciones simultáneas que se ejecutan de esta forma son atómicas, es decir, todas se aplican correctamente o todas fallan.

Borra datos

La forma más sencilla de borrar datos es llamar a RemoveValue() en una referencia a la ubicación de los datos.

También puedes borrar datos si especificas null Variant como el valor de otra operación de escritura, como SetValue() o UpdateChildren(). Puedes usar esta técnica con UpdateChildren() para borrar varios objetos secundarios con una sola llamada a la API.

Conoce cuándo se envían los datos

Para saber cuándo se envían los datos al servidor de Firebase Realtime Database, verifica que el resultado de Future sea correcto.

Guarda datos como transacciones

Cuando trabajas con datos que se podrían dañar si se hacen cambios simultáneos (por ejemplo, contadores incrementales) puedes usar una operación de transacción. A esta operación debes asignarle una función DoTransaction. La función de actualización toma el estado actual de los datos como argumento y da como resultado el nuevo estado que deseas escribir. Si otro cliente escribe en la ubicación antes de que tu valor nuevo se escriba correctamente, se hace una nueva llamada a la función de actualización con el nuevo valor actual y se vuelve a intentar la operación de escritura.

Por ejemplo, en un juego, podrías permitir que los usuarios actualicen una tabla de clasificación con las cinco puntuaciones más altas:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() >= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector<MutableData> children = mutableData.children();
      std::vector<MutableData>::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 < minScore) {
          minScore = childScore;
          minVal = &*it;
        }
      }
      if (minScore > 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<std::string, Variant> newScoreMap =
      new std::map<std::string, Variant>();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

Si usas una transacción, impides que la tabla de clasificación sea incorrecta en caso de que varios usuarios registren puntuaciones al mismo tiempo o el cliente tenga datos inactivos. Si se rechaza la transacción, el servidor le muestra el valor actual al cliente, que vuelve a ejecutar la transacción con el valor actualizado. Esto se repite hasta que se acepte la transacción o hasta que se registren demasiados intentos.

Escribe datos sin conexión

Si un cliente pierde la conexión de red, la app continúa funcionando de manera correcta.

Todos los clientes conectados a una base de datos de Firebase mantienen su propia versión interna de los datos activos. Cuando se escriben datos, se hace primero en esta versión local. Después, el cliente de Firebase sincroniza esos datos con los servidores de bases de datos remotas y con otros clientes según el “mejor esfuerzo”.

Como resultado, todas las operaciones de escritura en la base de datos activan eventos locales al instante, antes de que se escriban datos en el servidor. Esto significa que la app conserva la capacidad de respuesta, sin importar la latencia o el estado de conexión de la red.

Cuando se restablece la conectividad, la app recibe el conjunto de eventos adecuado, de manera que el cliente se sincroniza con el estado actual del servidor sin tener que escribir código personalizado.

Próximos pasos