🦄

Firestoreにデータを保存する - UnityでSDKなしでFirebaseを使う

6 min read

この投稿で解説する内容

サンプルとしてユーザー毎にメモの作成と一覧表示をする仕組みを作りながら、UnityからFirestoreを使う方法を解説していきます。

Firestoreとは

FirestoreはFirebaseのデータベースサービスです。
一般的なリレーショナルデータベース(RDB)とは違いドキュメントとコレクションの二つの要素によって構成されています。

この投稿ではUnityからFirestoreを使う事を主題に進めるため、Firestoreのわかりやすさを重視し厳密な説明をさけ意図的に不正確な内容になっています。詳細については公式ドキュメント等をご参照ください。

ドキュメント

ドキュメントはRDBのレコードやC#のオブジェクトに似た要素です。ドキュメントはjsonとして扱われstringやnumber、後述するコレクションなどをフィールドとして複数持つ事ができます。

今回のサンプルのメモをドキュメントとして表した例です。

{
  "memoNo": 1,
  "title": "ドキュメントのサンプル",
  "text": "ドキュメントはJsonに似た形式"
}

コレクション

コレクションはRDBテーブルやC#のMapに近い要素で、1つのコレクションはIDの異なる複数のドキュメントを持つ事ができます。コレクションはドキュメントとは異なり直接フィールドやコレクションを持つことはできません。
階層構造を持たせたい場合はコレクションに含まれるドキュメントにコレクションのフィールドを持たせることで階層構造を作れます。[1]この階層構造は以下のようなPathで表すことができデータの追加や取得時、アクセス制限の指定などに利用します。

コレクション名/ドキュメントID/コレクション名/ドキュメントID/...

今回のサンプルのメモは以下のPathにメモのドキュメントを追加していきます。

/users/{ユーザーID}/memos/{メモID(乱数)}

Firestoreの初期設定

まずFirestoreが使用できる状態になっているか確認します。
Webの管理画面のサイドメニューからFirestore Databaseを選択してください。もしデータのタブが表示されていれば設定済みなので次のセキュリティの設定に進んでください。

データベースの作成ボタンを押します

セキュリティー保護ルールは本番環境モードを選択して次へ

ロケーションはasia-northeast1を選択して次へ

以上でFirestoreが有効になります。

セキュリティルールの設定

Webの管理画面のFirestoreのルールタブではFirestoreに対するアクセス制限のルールを指定します。以下ルールで上書きして、公開ボタンを押してください。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {    
    match /users/{userID}/{document=**} {
      allow read, write: if request.auth.uid == userID; 
    }
  }
}

上記の設定はFirebaseの認証済みのユーザーにはusersコレクション配下のuserIDと同じ名前のドキュメントの読み書きを許すという内容です。ルールの設定方法についてはの詳細は公式ドキュメントや他の解説記事などをご参照ください。

Unityの実装

メモの追加、メモの一覧取得の処理を実装していきます。今回のサンプルではユーザー毎にメモを管理するため、ユーザーがログイン済みであることを前提に実装します。Firebaseにログイン方法については こちらの投稿を参考にしてください。

また、ソースコードの全量は以下のリポジトリにあります。

https://github.com/satouso0401/firebase-unity-not-use-sdk/tree/main/Assets/Scenes/Firestore

ドキュメントの登録

ドキュメントを登録する際の流れは以下の通りです。処理内容の補足についてはコードの後に記載します。

// メモに登録する内容
var title = GameObject.Find("TitleInput").GetComponent<InputField>().text;
var memoNo = Convert.ToInt32(GameObject.Find("MemoNoInput").GetComponent<InputField>().text);
var text = GameObject.Find("TextInput").GetComponent<InputField>().text;

// 認証情報の設定
WebClient wc = new WebClient();
wc.Headers[HttpRequestHeader.ContentType] = "application/json";
wc.Headers[HttpRequestHeader.Authorization] = $"Bearer {IDToken}"; // 事前に認証しユーザー毎のIDTokenを使用する

// ドキュメントの保存先の指定
var url =
    $"https://firestore.googleapis.com/v1/projects/{ProjectId}/databases/(default)/documents/users/{Uid}/memos";

// 保存するドキュメントの内容の指定
var requestBody = JsonUtility.ToJson(MemoCreateDocumentRequest.Apply(title, memoNo, text));
Debug.Log(requestBody);

// 登録処理
var responseJson = wc.UploadString(new Uri(url), requestBody);
Debug.Log(responseJson);

ドキュメントの保存先の指定

ドキュメントの保存先はFirestoreのAPIを叩く際のURLのPathで指定します。URLは Firestore用のAPIのurl(固定) + Firestore上の保存先のPath となっています。サンプルコードの例では https://firestore.googleapis.com/v1/projects/プロジェクトID/databases/(default)/documents までが固定部分で /users/ユーザーID/memos 部分がFirestoreの保存先のPathになっています。

保存されたドキュメントはWeb管理ツール上のFirestoreのデータタブでも確認することができます。

保存するドキュメントの内容の指定

登録時は各フィールドの型情報Firestoreに教えるために、単純なJsonではない形式でドキュメントを送る必要があります。サンプルコードのrequestBodyは以下のJsonになっています。

{
  "fields": {
    "title": {
      "stringValue": "タイトル"
    },
    "memoNo": {
      "integerValue": "1"
    },
    "text": {
      "stringValue": "メモの内容"
    }
  }
}

例えばmemoNoフィールドは単純に "memoNo": 1 とはせず、型を表すため"memoNo": {"integerValue": "1"}を設定します。文字列や整数の他にどういった型が指定できるかは公式ドキュメントを参照ください。

ドキュメントの検索

ドキュメントを検索する際の流れは以下の通りです。処理内容の補足についてはコードの後に記載します。

// 認証情報の設定
WebClient wc = new WebClient();
wc.Headers[HttpRequestHeader.ContentType] = "application/json";
wc.Headers[HttpRequestHeader.Authorization] = $"Bearer {IDToken}";

// 検索対象の指定
var url =
    $"https://firestore.googleapis.com/v1/projects/{ProjectId}/databases/(default)/documents/users/{Uid}:runQuery";

// 検索条件の指定
var requestBody = new FirestoreApi.Query("memos").ToJson();
Debug.Log(requestBody);

// 検索結果
var responseJson = wc.UploadString(new Uri(url), requestBody);
Debug.Log(responseJson);

// 検索結果の表示
var memos = MemoRunQueryResponseWrapper.FromJson(responseJson);
var titleList = memos.Aggregate("", (acc, memo) => acc + memo.document.fields.title.stringValue + "\n");
var memoList = GameObject.Find("MemoList").GetComponent<Text>();
memoList.text = titleList;

検索対象の指定

検索対象の指定はAPIを叩く際のURLのPathと後述のリクエストボディを組み合わせて指定します。URLは Firestore用のAPIのurl(固定) + Firestore上のドキュメントのPath + :runQuery の形式になっています。サンプルでは /users/ユーザーIDの部分がドキュメントのPathを表しています。

検索条件の指定

リクエストボディに指定する検索条件は以下のJsonを指定します。検索したいドキュメントが含まれるコレクションをcollectionIdに指定します。前述のURLで指定したドキュメントのPathと、このcollectionIdを結合すると検索対象になるコレクションの絶対パスになるイメージです。

{
  "structuredQuery": {
    "from": [
      {
        "collectionId": "memos"
      }
    ]
  }
}

このほか取得件数の上限指定やソートなどを指定することもできます。もしそれらを使用したい場合は公式ドキュメントでGUI上でJsonを組み立てられるので、サンプルコードと見比べつつ実装してください。

脚注
  1. C#で List<Foo> foos という変数があった場合、foosにはList<List<Bar>>型の値を入れられないので、FooクラスのフィールドにList<Bar>型のフィールドを定義するイメージです ↩︎

Discussion

ログインするとコメントできます