Menyimpan Data dengan Firebase Realtime Database untuk C++

Memulai

Lihat panduan Get Started terlebih dahulu jika Anda belum menyiapkan aplikasi dan akses ke database.

Mendapatkan DatabaseReference

Untuk menulis data ke Database, Anda perlu instance DatabaseReference:

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

Menyimpan Data

Ada empat metode untuk menulis data ke Firebase Realtime Database:

Metode Penggunaan umum
SetValue() Menulis atau mengganti data ke jalur yang ditetapkan, seperti users/<user-id>/<username>.
PushChild() Menambahkan ke daftar data. Setiap kali Anda memanggil Push(), Firebase akan menghasilkan kunci unik yang juga dapat digunakan sebagai ID unik, seperti user-scores/<user-id>/<unique-score-id>.
UpdateChildren() Mengupdate beberapa kunci untuk jalur yang ditetapkan tanpa mengganti semua data.
RunTransaction() Mengupdate data kompleks yang bisa rusak karena update serentak.

Menulis, mengupdate, atau menghapus data pada referensi

Operasi tulis dasar

Untuk operasi tulis dasar, Anda dapat menggunakan SetValue() untuk menyimpan data ke referensi yang ditentukan, sehingga menggantikan data yang ada di jalur tersebut. Anda dapat menggunakan metode ini untuk meneruskan jenis yang diterima oleh JSON melalui jenis Varian yang mendukung:

  • Null (ini menghapus data)
  • Bilangan bulat (64-bit)
  • Angka double precision floating point
  • Boolean
  • String
  • Vektor Varian
  • Peta string ke Varian

Penggunaan SetValue() seperti ini akan menimpa data di jalur yang ditentukan, termasuk semua node turunan. Namun, Anda masih dapat memperbarui turunan tanpa menulis ulang seluruh objek. Jika ingin mengizinkan pengguna mengupdate profil mereka, Anda dapat mengupdate nama pengguna seperti berikut:

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

Menambahkan ke daftar data

Gunakan metode PushChild() untuk menambahkan data ke daftar dalam aplikasi multipengguna. Metode PushChild() menghasilkan kunci unik setiap kali turunan baru ditambahkan ke referensi Firebase tertentu. Dengan kunci yang dihasilkan secara otomatis untuk setiap elemen baru dalam daftar, beberapa klien dapat menambahkan turunan ke lokasi yang sama secara bersamaan tanpa mengalami konflik penulisan. Kunci unik yang dihasilkan oleh PushChild() didasarkan pada stempel waktu, sehingga daftar item otomatis diurutkan secara kronologis.

Anda dapat menggunakan referensi pada data baru yang ditampilkan oleh metode PushChild() untuk mendapatkan nilai kunci turunan yang dihasilkan otomatis atau menetapkan data untuk turunan. Memanggil GetKey() pada referensi PushChild() akan menampilkan nilai kunci yang dihasilkan secara otomatis.

Mengupdate kolom tertentu

Untuk menulis secara simultan ke turunan tertentu dari sebuah node tanpa menimpa node turunan yang lain, gunakan metode UpdateChildren().

Saat memanggil UpdateChildren(), Anda dapat memperbarui nilai turunan di level yang lebih rendah dengan menetapkan jalur untuk kunci tersebut. Jika data disimpan dalam beberapa lokasi agar dapat melakukan penskalaan yang lebih baik, Anda dapat mengupdate semua instance data tersebut menggunakan fan-out data. Misalnya, suatu game mungkin memiliki class LeaderboardEntry seperti ini:

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;
  }
}

Untuk membuat LeaderboardEntry sekaligus mengupdatenya ke feed skor terbaru dan daftar skor milik pengguna, game menggunakan kode berikut:

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);
}

Contoh ini menggunakan PushChild() untuk membuat entri dalam node yang berisi entri untuk semua pengguna di /scores/$key, sekaligus mengambil kunci dengan key(). Selanjutnya, kunci tersebut dapat digunakan untuk membuat entri kedua di skor pengguna pada /user-scores/$userid/$key.

Dengan menggunakan jalur tersebut, Anda dapat menjalankan update simultan ke beberapa lokasi di hierarki JSON dengan sekali panggilan ke UpdateChildren(), seperti yang digunakan pada contoh ini untuk membuat entri baru di kedua lokasi. Update bersamaan menjadikan proses ini berjalan menyeluruh: entah semua update berhasil atau semua update gagal.

Menghapus data

Cara termudah untuk menghapus data adalah dengan memanggil RemoveValue() pada referensi ke lokasi data tersebut.

Penghapusan juga dapat dilakukan dengan menentukan null Variant sebagai nilai untuk operasi tulis lainnya, seperti SetValue() atau UpdateChildren(). Teknik ini dapat digunakan dengan UpdateChildren() untuk menghapus beberapa turunan dengan sebuah panggilan API.

Mengetahui kapan data disimpan.

Untuk mengetahui kapan data Anda disimpan ke server Firebase Realtime Database, periksa keberhasilannya di hasil Future.

Menyimpan data sebagai transaksi

Ketika bekerja dengan data yang bisa rusak karena perubahan serentak, seperti penghitung tambahan, Anda dapat menggunakan operasi transaksi. Anda memberikan fungsi DoTransaction pada operasi ini. Fungsi update ini akan mengambil status data saat ini sebagai argumen, dan menampilkan status baru yang diinginkan untuk Anda tulis. Jika ada klien lain yang melakukan penulisan ke lokasi sebelum nilai yang baru berhasil ditulis, fungsi update Anda akan dipanggil lagi dengan nilai baru saat ini, dan proses tulis akan dicoba ulang.

Misalnya, dalam sebuah game, Anda dapat mengizinkan pengguna untuk mengupdate papan peringkat dengan lima skor tertinggi:

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;
  });
}

Penggunaan transaksi dapat mencegah kesalahan papan peringkat jika beberapa pengguna mencatat skor secara bersamaan atau jika klien memiliki data yang sudah usang. Jika transaksi ditolak, server akan menampilkan nilai saat ini ke klien yang akan menjalankan lagi transaksi tersebut dengan nilai yang diupdate. Proses ini akan berulang hingga transaksi diterima atau ada terlalu banyak percobaan yang dilakukan.

Menulis data offline

Jika koneksi jaringan klien terputus, aplikasi Anda akan tetap berfungsi dengan baik.

Setiap klien yang terhubung ke database Firebase menyimpan versi internalnya sendiri untuk setiap data aktif. Ketika ditulis, data akan dituliskan ke versi lokal ini terlebih dahulu. Selanjutnya, klien Firebase menyinkronkan data tersebut dengan server remote database, dan dengan klien lain berdasarkan "upaya terbaik".

Akibatnya, semua operasi tulis ke database akan langsung memicu peristiwa lokal, sebelum ada data yang dituliskan ke server. Ini berarti aplikasi Anda akan tetap responsif, apa pun kondisi konektivitas atau latensi jaringannya.

Setelah konektivitas pulih, aplikasi Anda akan menerima kumpulan peristiwa yang tepat, sehingga klien sinkron dengan kondisi server saat ini, tanpa perlu menulis kode khusus apa pun.

Langkah Berikutnya