🪶

Firestoreの公式を見ながらSwiftUIに機能を追加してみた

2024/05/09に公開

🤔やってみたいこと

Flutterでは普段はモデルクラスを作って、Firestoreに.add.setしているがSwfitだとうまくいかない💦

公式には、Swiftのライフサイクルと配列の操作まで必要なことは書いてなかったりする。
https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja

今回はドキュメントのコードを使いながらデータを追加できるのを試してみようと思う。setDataaddDocumentで、structをそのまま引数で渡しても使えなかったでどうすればいいのか記事にすることにした。他にも方法はあるみたいですが...

🚀やってみたこと

まずは公式のコードを参考にロジックを考えてみた。Cityという構造体を作ってみる。サンプルのままだとプロパティが多いので、nameだけにした。

enumのCodingKeyは内部実装を見るとこのように書かれておりました。

A type that can be used as a key for encoding and decoding.
エンコードおよびデコードのキーとして使用できるタイプ。

setDataの場合だとこのままでも渡せる。

public struct City: Codable {

  let name: String

    enum CodingKeys: String, CodingKey {
        case name
    }
}

こちらが一度だけデータを書き込みして、2回目からは上書きするメソッド。プロフィールとか特定の情報を作るときに使われるのではないでしょうか💁‍♀️

// use case of setData
    func setCity(city: City) async {
        do {
            try db.collection("cities").document("LA").setData(from: city)
        } catch let error {
          print("Error writing city to Firestore: \(error)")
        }
    }

何度でも書き込むことができて、ランダムなIDが生成されるaddDocumentはTodoアプリやChatアプリで使われると思います。この場合は、特定のデータを編集・削除するには、View内で、DocumentIDを取得する必要があります。

structを加工しないと引数として渡せなかったので、Firestore.Encoder().encode(city)を使う必要がありました。

// use case of addDocument
    func addCity(city: City) async {
        // Add a new document with a generated id.
        do {
            let cityData = try Firestore.Encoder().encode(city)
          let ref = try await db.collection("cities").addDocument(data: cityData)
          print("Document added with ID: \(ref.documentID)")
        } catch {
          print("Error adding document: \(error)")
        }
    }

[メソッドを書いた全体のコード]

import FirebaseFirestore
// City Class
class CityRepository : ObservableObject {
    // instance of Firestore
    let db = Firestore.firestore()
    
    // use case of setData
    func setCity(city: City) async {
        do {
            try db.collection("cities").document("LA").setData(from: city)
        } catch let error {
          print("Error writing city to Firestore: \(error)")
        }
    }
    
    // use case of addDocument
    func addCity(city: City) async {
        // Add a new document with a generated id.
        do {
            let cityData = try Firestore.Encoder().encode(city)
          let ref = try await db.collection("cities").addDocument(data: cityData)
          print("Document added with ID: \(ref.documentID)")
        } catch {
          print("Error adding document: \(error)")
        }
    }
}

[View側のコード]
入力をして、1度だけ追加か何個でも追加できるボタンを配置してます。これで保存するとデータの保存のされ方が異なると思います。

import SwiftUI

struct CityView: View {
    // input value
    @State private var name: String = ""
    
    // UserRepository instance
    @ObservedObject private var cityRepository = CityRepository()
    
    var body: some View {
        VStack {
            TextField("City Name", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()
            
            Button(action: {
                Task {
                    let city = City(name: name.uppercased())
                    await cityRepository.setCity(city: city)
                }
            }) {
                Text("setCity")
            }
            .foregroundColor(.white)
                            .buttonStyle(.borderedProminent)
                            .tint(.red)
            .padding()
            Button(action: {
                Task {
                    let city = City(name: name.uppercased())
                    await cityRepository.addCity(city: city)
                }
            }) {
                Text("addCity")
            }
            .foregroundColor(.white)
                            .buttonStyle(.borderedProminent)
                            .tint(.blue)
            .padding()
        }
    }
}

#Preview {
    CityView()
}

試しに使ってみるとこんな感じになります。setDataだけ変ですが笑

🙂最後に

公式を見ながらやってるのですが、やはりチュートリアルの動画や技術記事を見ないと、足りないコードもあるのでわからなかったですね😇
SwiftUIだと、UIKitより簡単に書けるけど、配列を操作したりページが呼ばれたらロジックを実行するライフサイクルを理解しないといけない場面があるので簡単ではないですね。

足りなかったコードだとこれかな。そのとのきによってパターンは異なりますね。

  1. Task {}
  2. CodingKey
  3. Firestore.Encoder().encode(city)

Discussion