【Firebase】ドキュメントの更新

本日はFirebaseのCloud Storeにおけるデータの更新の仕方をまとめようと思います。

自分のアプリの品質強化施策で、ドキュメント更新失敗時のケースを検討しようとソースを見てたのですが、パッと見て実装に問題があるのかわかりませんでした・・・
→実装の仕方をすっかり忘れてた!

ですので、復習がてらドキュメントの更新方法に関して、swiftでの実装方法を紹介しつつまとめてみようと思います。


以下サイトを参考に実装していますが、基本的には自分の使用した機能を紹介します。

firebase.google.com

前提

Cloud Storeを使用するにあたって、「コレクション」、「ドキュメント」、「フィールド」といったデータに関する概念の説明は省略します。

最初に - フィールドに格納できる情報について

文字列、ブール値、日付、NULL、ネストされた配列、オブジェクトなどを格納できます。

以下サイトにて使用できる型がまとまってます。
サポートされるデータ型  |  Firebase

Myトレの例

せっかくですので、私のアプリにおける「日々の記録」のデータ構造をご紹介します。
以下は実際にMyトレで、Cloud Storeにデータを格納する際に定義している変数になります。

フィールドに投入するデータは、swift上では[String: Any]型である必要があります。

実際にCloud Storeで管理する際には「map型」で管理するため[String: Any]型と言いつつも、intやtimestamp型が登場しています。

let docData: [String: Any] = [
    "taskData": [
        "腕立て伏せ": [
            "1": [
              "count": 15,  // Int型
              "date": Timestamp(date: "date型、作成した時刻") 
            ],
            "2": [
              "count": 17,  // Int型
              "date": Timestamp(date: "date型、作成した時刻") 
            ]
        ],
        "スクワット": [
          "1": [
            "count": 20,  // Int型
            "date": Timestamp(date: "date型、作成した時刻") 
          ]
       ]
    ]
]

コード実装

コレクション・ドキュメントの定義

以下のコードでが、実際にデータを格納したいコレクション、ドキュメントを定義しています。
後述しますが、以下定義した変数からsetDataなどのメソッドを呼び出し引数に上記の[String:Any]型の変数を設定することで、Cloud Storeにデータが格納できます。

import Firebase

var baseRef: DocumentReference? = nil
var baseCol: CollectionReference

baseCol = Firestore.firestore().collection("ルートで、すでに存在する、これから作るコレクション名を定義")
baseRef = baseCol.document("すでに存在する、これから作るドキュメント名を定義")

ドキュメントの作成

ドキュメントを新規に生成する場合は、以下の3つのパターンのどれかで実装します。

ドキュメント名を自分で定義する場合
baseCol.document("20201102").setData(docData)
ドキュメント名を自動で定義する場合
baseCol.addDocument(data: docData)
すでにドキュメントが存在する場合の対応
baseCol.document("20201102").setData(docData, merge: [true or false])

すでにドキュメントが存在する場合にsetDataをすると、新しいドキュメントで上書きされてしまいます(既存のドキュメント・フィールドが消える)。
mergeオプションをtrueにすることで、既存のデータを消さずに新しいデータを統合することが可能となります。

ドキュメントを更新する

以下の例は、上記のsetDataで投入したデータの一部を更新します。

let docUpdateData: [String: Any] = [
    "taskData": [
        "スクワット": [
          1: [
            count: 25,
            date: 20201202 17:35:30
          ]
    ]
]

baseCol.updateData(docUpdateData) { err in
    if let err = err {
        print("Error updating document: \(err)")
    } else {
        print("Document successfully updated")
    }
}

補足
updateData = setData(data, merge: true)なのかなと思ってます。実際に検証したことがないので、挙動に差異があるか機会があれば別途調べてみようと思います。
ですが基本的には、初回のデータ投入:setData、それ以降のデータ更新:updateData、という考えで問題ないと思います。

最後に

データの更新自体は、上記程度のコード量で済んでしまうのでぶっちゃけ楽です!
私は、最初に実装したのはもう一年くらいになりますが、初めてデータを投入できたときは感動しました。

おおー、データ投入できた!!!ってめちゃめちゃ喜んでました笑

ただ、どういった構造にしていくか?検討するフェーズが難関であると思います。
なんやかんやで1ヶ月くらいはトライアンドエラーで実装検証してました・・・

また、セキュリティルールも同時に検討していかないと、大事な個人データが勝手に流出されてしまうかもです。
→セキュリティに関しては、次回別途何か記事を作ろうかなと思ってます!


そんなわけで、ドキュメントの更新方法についてでした!