Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

데이터를 저장

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

시작하기 전에

실시간 데이터베이스 를 사용하려면 먼저 다음을 수행해야 합니다.

  • Unity 프로젝트를 등록하고 Firebase를 사용하도록 구성합니다.

    • Unity 프로젝트에서 이미 Firebase를 사용하고 있다면 이미 Firebase용으로 등록 및 구성되어 있는 것입니다.

    • Unity 프로젝트가 없는 경우 샘플 앱 을 다운로드할 수 있습니다.

  • Firebase Unity SDK (특히 FirebaseDatabase.unitypackage )를 Unity 프로젝트에 추가합니다.

Unity 프로젝트에 Firebase를 추가하려면 Firebase 콘솔 과 열려 있는 Unity 프로젝트 모두의 작업이 필요합니다(예: 콘솔에서 Firebase 구성 파일을 다운로드한 다음 Unity 프로젝트로 이동).

데이터 저장

Firebase 실시간 데이터베이스에 데이터를 쓰는 방법에는 다섯 가지가 있습니다.

방법 일반적인 용도
SetValueAsync() users/<user-id>/<username> 과 같은 정의된 경로에 데이터를 쓰거나 바꿉니다.
SetRawJsonValueAsync() users/<user-id>/<username> 과 같은 원시 Json으로 데이터를 쓰거나 바꿉니다.
Push() 데이터 목록에 추가합니다. Push() 를 호출할 때마다 Firebase는 user-scores/<user-id>/<unique-score-id> 와 같은 고유 식별자로도 사용할 수 있는 고유 키를 생성합니다.
UpdateChildrenAsync() 모든 데이터를 바꾸지 않고 정의된 경로에 대한 일부 키를 업데이트합니다.
RunTransaction() 동시 업데이트로 인해 손상될 수 있는 복잡한 데이터를 업데이트합니다.

DatabaseReference 가져오기

데이터베이스에 데이터를 쓰려면 DatabaseReference 인스턴스가 필요합니다.

using Firebase;
using Firebase.Database;

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

참조에서 데이터 쓰기, 업데이트 또는 삭제

기본 쓰기 작업

기본 쓰기 작업의 경우 SetValueAsync() 를 사용하여 지정된 참조에 데이터를 저장하고 해당 경로의 기존 데이터를 바꿀 수 있습니다. 이 방법을 사용하여 다음과 같이 사용 가능한 JSON 유형에 해당하는 유형을 전달할 수 있습니다.

  • string
  • long
  • double
  • bool
  • Dictionary<string, Object>
  • List<Object>

형식이 지정된 C# 개체를 사용하는 경우 기본 JsonUtility.ToJson() 을 사용하여 개체를 원시 Json으로 변환하고 SetRawJsonValueAsync SetRawJsonValueAsync() ) 를 호출할 수 있습니다. 예를 들어 다음과 같은 User 클래스가 있을 수 있습니다.

public class User {
    public string username;
    public string email;

    public User() {
    }

    public User(string username, string email) {
        this.username = username;
        this.email = email;
    }
}

다음과 같이 SetRawJsonValueAsync() 를 사용하여 사용자를 추가할 수 있습니다.

private void writeNewUser(string userId, string name, string email) {
    User user = new User(name, email);
    string json = JsonUtility.ToJson(user);

    mDatabaseRef.Child("users").Child(userId).SetRawJsonValueAsync(json);
}

이러한 방식으로 SetValueAsync() 또는 SetRawJsonValueAsync() 를 사용하면 자식 노드를 포함하여 지정된 위치의 데이터를 덮어씁니다. 그러나 전체 개체를 다시 작성하지 않고도 자식을 업데이트할 수 있습니다. 사용자가 프로필을 업데이트하도록 허용하려면 다음과 같이 사용자 이름을 업데이트할 수 있습니다.

mDatabaseRef.Child("users").Child(userId).Child("username").SetValueAsync(name);

데이터 목록에 추가

Push() 메서드를 사용하여 다중 사용자 응용 프로그램의 목록에 데이터를 추가합니다. Push() 메서드는 지정된 Firebase 참조에 새 하위 항목이 추가될 때마다 고유 키를 생성합니다. 목록의 각 새 요소에 대해 이러한 자동 생성 키를 사용하여 여러 클라이언트가 쓰기 충돌 없이 동시에 동일한 위치에 자식을 추가할 수 있습니다. Push() 에 의해 생성된 고유 키는 타임스탬프를 기반으로 하므로 목록 항목은 자동으로 시간순으로 정렬됩니다.

Push() 메서드에서 반환된 새 데이터에 대한 참조를 사용하여 자식의 자동 생성 키 값을 가져오거나 자식에 대한 데이터를 설정할 수 있습니다. Push() 참조에서 Key 를 호출하면 자동 생성된 키의 값이 반환됩니다.

특정 필드 업데이트

다른 자식 노드를 덮어쓰지 않고 노드의 특정 자식에 동시에 쓰려면 UpdateChildrenAsync() 메서드를 사용합니다.

UpdateChildrenAsync() 를 호출할 때 키의 경로를 지정하여 하위 수준 자식 값을 업데이트할 수 있습니다. 데이터가 더 나은 확장성을 위해 여러 위치에 저장되어 있는 경우 데이터 팬아웃 을 사용하여 해당 데이터의 모든 인스턴스를 업데이트할 수 있습니다. 예를 들어 게임에는 다음과 같은 LeaderboardEntry 클래스가 있을 수 있습니다.

public class LeaderboardEntry {
    public string uid;
    public int score = 0;

    public LeaderboardEntry() {
    }

    public LeaderboardEntry(string uid, int score) {
        this.uid = uid;
        this.score = score;
    }

    public Dictionary<string, Object> ToDictionary() {
        Dictionary<string, Object> result = new Dictionary<string, Object>();
        result["uid"] = uid;
        result["score"] = score;

        return result;
    }
}

LeaderboardEntry를 생성하고 동시에 최근 점수 피드와 사용자 자신의 점수 목록으로 업데이트하기 위해 게임은 다음과 같은 코드를 사용합니다.

private void WriteNewScore(string userId, int score) {
    // Create new entry at /user-scores/$userid/$scoreid and at
    // /leaderboard/$scoreid simultaneously
    string key = mDatabase.Child("scores").Push().Key;
    LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
    Dictionary<string, Object> entryValues = entry.ToDictionary();

    Dictionary<string, Object> childUpdates = new Dictionary<string, Object>();
    childUpdates["/scores/" + key] = entryValues;
    childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

    mDatabase.UpdateChildrenAsync(childUpdates);
}

이 예제에서는 Push() 를 사용하여 /scores/$key 의 모든 사용자에 대한 항목이 포함된 항목을 노드에 생성하고 동시에 Key 를 사용하여 키를 검색합니다. 그런 다음 키를 사용하여 /user-scores/$userid/$key 의 사용자 점수에 두 번째 항목을 만들 수 있습니다.

이러한 경로를 사용하면 UpdateChildrenAsync() 에 대한 단일 호출로 JSON 트리의 여러 위치에 대한 동시 업데이트를 수행할 수 있습니다(예: 이 예제가 두 위치에서 새 항목을 생성하는 방법). 이러한 방식으로 이루어진 동시 업데이트는 원자적입니다. 모든 업데이트가 성공하거나 모든 업데이트가 실패합니다.

데이터 삭제

데이터를 삭제하는 가장 간단한 방법은 해당 데이터의 위치에 대한 참조에서 RemoveValue() 를 호출하는 것입니다.

SetValueAsync() 또는 UpdateChildrenAsync() 와 같은 다른 쓰기 작업의 값으로 null 을 지정하여 삭제할 수도 있습니다. UpdateChildrenAsync() 와 함께 이 기술을 사용하여 단일 API 호출에서 여러 하위 항목을 삭제할 수 있습니다.

데이터가 커밋된 시점을 알 수 있습니다.

데이터가 Firebase 실시간 데이터베이스 서버에 커밋된 시기를 알기 위해 연속을 추가할 수 있습니다. SetValueAsync()UpdateChildrenAsync() 모두 작업이 완료될 때 알 수 있는 Task 을 반환합니다. 어떤 이유로든 호출이 실패하면 실패가 발생한 이유를 나타내는 Exception 속성과 함께 Tasks IsFaulted 가 true가 됩니다.

데이터를 트랜잭션으로 저장

증분 카운터와 같이 동시 수정으로 인해 손상될 수 있는 데이터로 작업할 때 트랜잭션 작업 을 사용할 수 있습니다. 이 작업에 Func 를 제공합니다. 이 업데이트 Func 는 데이터의 현재 상태를 인수로 사용하고 작성하려는 새로운 원하는 상태를 반환합니다. 새 값이 성공적으로 기록되기 전에 다른 클라이언트가 위치에 쓰는 경우 업데이트 함수가 새 현재 값으로 다시 호출되고 쓰기가 재시도됩니다.

예를 들어 게임에서 사용자가 최고 점수 5개로 순위표를 업데이트하도록 허용할 수 있습니다.

private void AddScoreToLeaders(string email, 
                               long score,
                               DatabaseReference leaderBoardRef) {

    leaderBoardRef.RunTransaction(mutableData => {
      List<object> leaders = mutableData.Value as List<object>

      if (leaders == null) {
        leaders = new List<object>();
      } else if (mutableData.ChildrenCount >= MaxScores) {
        long minScore = long.MaxValue;
        object minVal = null;
        foreach (var child in leaders) {
          if (!(child is Dictionary<string, object>)) continue;
          long childScore = (long)
                      ((Dictionary<string, object>)child)["score"];
          if (childScore < minScore) {
            minScore = childScore;
            minVal = child;
          }
        }
        if (minScore > score) {
          // The new score is lower than the existing 5 scores, abort.
          return TransactionResult.Abort();
        }

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

      // Add the new high score.
      Dictionary<string, object> newScoreMap =
                       new Dictionary<string, object>();
      newScoreMap["score"] = score;
      newScoreMap["email"] = email;
      leaders.Add(newScoreMap);
      mutableData.Value = leaders;
      return TransactionResult.Success(mutableData);
    });
}

트랜잭션을 사용하면 여러 사용자가 동시에 점수를 기록하거나 클라이언트에 오래된 데이터가 있는 경우 리더보드가 부정확해지는 것을 방지할 수 있습니다. 트랜잭션이 거부되면 서버는 현재 값을 클라이언트에 반환하고 클라이언트는 업데이트된 값으로 트랜잭션을 다시 실행합니다. 이것은 트랜잭션이 수락되거나 너무 많은 시도가 이루어질 때까지 반복됩니다.

오프라인으로 데이터 쓰기

클라이언트의 네트워크 연결이 끊어져도 앱은 계속해서 올바르게 작동합니다.

Firebase 데이터베이스에 연결된 모든 클라이언트는 활성 데이터의 자체 내부 버전을 유지 관리합니다. 데이터가 기록되면 이 로컬 버전에 먼저 기록됩니다. 그런 다음 Firebase 클라이언트는 해당 데이터를 "최선의" 방식으로 원격 데이터베이스 서버 및 다른 클라이언트와 동기화합니다.

결과적으로 데이터베이스에 대한 모든 쓰기는 데이터가 서버에 쓰기 전에 즉시 로컬 이벤트를 트리거합니다. 즉, 네트워크 대기 시간이나 연결에 관계없이 앱이 응답하는 상태를 유지합니다.

연결이 다시 설정되면 사용자 지정 코드를 작성할 필요 없이 클라이언트가 현재 서버 상태와 동기화할 수 있도록 앱이 적절한 이벤트 집합을 수신합니다.

다음 단계