Rozpocznij
Jeśli nie masz jeszcze skonfigurowanej aplikacji i dostępu do bazy danych, najpierw zapoznaj się z poradnikiem Get Started
.
Pobieranie obiektu DatabaseReference
Aby zapisać dane w bazie danych, potrzebujesz instancji DatabaseReference
:
// Get the root reference location of the database. firebase::database::DatabaseReference dbref = database->GetReference();
Zapisywanie danych
Dane do pliku Firebase Realtime Database można zapisywać na 4 sposoby:
Metoda | Typowe zastosowania |
---|---|
SetValue() |
zapisywać lub zastępować dane w określonej ścieżce, takiej jak
users/<user-id>/<username> . |
PushChild() |
Dodaj do listy danych. Za każdym razem, gdy wywołujesz funkcję Push() , Firebase generuje unikalny klucz, który może też służyć jako unikalny identyfikator, np. user-scores/<user-id>/<unique-score-id> . |
UpdateChildren() |
zaktualizować niektóre klucze na zdefiniowanej ścieżce bez zastępowania wszystkich danych; |
RunTransaction() |
Aktualizowanie złożonych danych, które mogą zostać uszkodzone przez równoczesne aktualizacje. |
zapisywać, aktualizować i usuwać dane w referencji;
Podstawowe operacje zapisu
W przypadku podstawowych operacji zapisu możesz użyć funkcji SetValue()
, aby zapisać dane w określonym odwołaniu, zastępując wszystkie istniejące dane na tej ścieżce. Za pomocą tej metody możesz przekazywać typy akceptowane przez JSON za pomocą typu Wariant, który obsługuje:
- null (powoduje usunięcie danych);
- Liczba całkowita (64-bitowa)
- liczby zmiennoprzecinkowe podwójnej precyzji,
- Wartości logiczne
- Strings
- Wektory wariantów
- Mapowanie ciągów znaków na warianty
Użycie w ten sposób wartości SetValue()
spowoduje zastąpienie danych w wybranej lokalizacji, w tym wszystkich węzłów podrzędnych. Nadal jednak możesz zaktualizować element podrzędny bez przepisywania całego obiektu. Jeśli chcesz zezwolić użytkownikom na aktualizowanie ich profili, możesz zaktualizować nazwę użytkownika w ten sposób:
dbref.Child("users").Child(userId).Child("username").SetValue(name);
Dołączanie do listy danych
Aby dołączać dane do listy w aplikacji wielostanowiskowej, użyj metody PushChild()
.
Metoda PushChild()
generuje unikalny klucz za każdym razem, gdy do określonego odwołania Firebase dodawane jest nowe dziecko. Dzięki tym automatycznie generowanym kluczom dla każdego nowego elementu na liście kilka klientów może dodawać podelementy do tej samej lokalizacji w tym samym czasie bez konfliktów podczas zapisu. Unikalny klucz wygenerowany przez funkcję PushChild()
jest oparty na sygnaturze czasowej, więc elementy listy są automatycznie sortowane chronologicznie.
Możesz użyć odwołania do nowych danych zwróconych przez metodę PushChild()
, aby uzyskać wartość automatycznie wygenerowanego klucza podrzędnego lub ustawić dane dla tego klucza.
Wywołanie funkcji GetKey()
w przypadku odwołania PushChild()
zwraca wartość klucza wygenerowanego automatycznie.
Aktualizowanie konkretnych pól
Aby jednocześnie zapisywać dane w określonych węzłach podrzędnych bez nadpisywania innych węzłów podrzędnych, użyj metody UpdateChildren()
.
Podczas wywoływania funkcji UpdateChildren()
możesz aktualizować wartości podrzędne niższego poziomu, podając ścieżkę klucza. Jeśli dane są przechowywane w kilku lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie wystąpienia tych danych za pomocą rozgałęzienia danych. Na przykład gra może mieć klasę LeaderboardEntry
, która wygląda tak:
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; } }
Aby utworzyć LeaderboardEntry
i jednocześnie zaktualizować go w bieżącym strumieniu wyników oraz na liście wyników użytkownika, gra używa tego kodu:
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); }
W tym przykładzie za pomocą parametru PushChild()
tworzony jest wpis w węźle zawierającym wpisy wszystkich użytkowników w węźle /scores/$key
, a jednocześnie pobierany jest klucz za pomocą parametru key()
. Klucza można użyć do utworzenia drugiego wpisu w tablicy wyników użytkownika na stronie /user-scores/$userid/$key
.
Dzięki tym ścieżkom możesz przeprowadzać jednoczesne aktualizacje wielu lokalizacji w drzewie JSON za pomocą jednego wywołania funkcji UpdateChildren()
. W tym przykładzie nowy wpis jest tworzony w obu lokalizacjach. Jednoczesne aktualizacje w ten sposób są atomowe: albo wszystkie się powiodą, albo wszystkie się nie powiodą.
Usuń dane
Najprostszym sposobem usuwania danych jest wywołanie funkcji RemoveValue()
z odniesieniem do lokalizacji tych danych.
Możesz też usunąć element, podając jako wartość dla innej operacji zapisu (np. SetValue()
lub UpdateChildren()
) wartość null
Variant
. Możesz użyć tej techniki za pomocą funkcji UpdateChildren()
, aby usunąć wiele elementów podrzędnych w jednym wywołaniu interfejsu API.
Dowiedz się, kiedy Twoje dane są zapisywane.
Aby dowiedzieć się, kiedy Twoje dane są zapisywane na serwerze Firebase Realtime Database, sprawdź wynik Przyszłość.
Zapisywanie danych jako transakcji
Podczas pracy z danymi, które mogą zostać uszkodzone przez równoległe modyfikacje, np. z licznikami przyrostowymi, możesz użyć operacji transakcji.
Ta operacja ma funkcję DoTransaction
. Ta funkcja aktualizacji przyjmuje jako argument bieżący stan danych i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli inny klient zapisze dane w danej lokalizacji, zanim nowa wartość zostanie zapisana, funkcja aktualizacji zostanie wywołana ponownie z nową bieżącą wartością i ponownym próbą zapisu.
W grze możesz na przykład zezwolić użytkownikom na aktualizowanie tabeli wyników za pomocą 5 najlepszych wyników:
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; }); }
Transakcja zapobiega błędom w tablicy liderów, jeśli wyniki rejestruje jednocześnie wielu użytkowników lub klient ma nieaktualne dane. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość klientowi, który ponownie wykona transakcję z aktualną wartością. Czynność ta jest powtarzana, dopóki transakcja nie zostanie zaakceptowana lub nie zostanie podjęta zbyt duża liczba prób.
Zapisywanie danych offline
Jeśli klient utraci połączenie z siecią, Twoja aplikacja będzie nadal działać prawidłowo.
Każdy klient połączony z bazą danych Firebase utrzymuje własną wersję wewnętrzną wszystkich aktywnych danych. Dane są najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami bazy danych i z innymi klientami według zasady „najlepszego wysiłku”.
W rezultacie wszystkie zapisy w bazie danych powodują natychmiastowe wywołanie zdarzeń lokalnych, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnień w sieci lub połączenia.
Po ponownym nawiązaniu połączenia aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera, bez konieczności pisania kodu niestandardowego.