🪶

SwiftUI Add Data fetch & delete のライフサイクルでハマった!

2024/05/08に公開

🤔やってみたいこと

SwiftUIで、Firestoreからデータの取得をして表示とゴミ箱のボタンを押して削除した時に画面が更新されない問題が起きた???
あるいは、配列を操作しないと画面の値を増やしたり削除できないのか???

Firestoreに合わせてモデルを定義

/// [Firestore Data Model]
/// `Codable` is a type alias for the `Encodable` and `Decodable` protocols.
struct UserModel: Codable {
    var id: String
    var name: String
    
    // Convert a UserModel into a Dictionaty
    func toDictionary() -> [String: Any] {
        return [
            "id": id,
            "name": name
        ]
    }
}

Firestoreを使うスーパークラスを定義

import FirebaseFirestore

// super class
class BaseRepository {
    // instance of Firestore
    let db = Firestore.firestore()
}

ロジックを書いたサブクラスを定義。データの追加、表示、削除ができる。追加の場合は、配列に値を追加、削除時は、配列の値を削除しないと画面の値が増えたり消えない!

import FirebaseFirestore
import Combine

class UserRepository : BaseRepository, ObservableObject {
    // fetch user data from Firestore
    @Published var users = [UserModel]()
    
    // get data
    func getData() async {
        do {
            let snapshot = try await db.collection("users").getDocuments()
            for document in snapshot.documents {
                let data = document.data()
                
                let user = UserModel(id: document.documentID, name: data["name"] as! String)
                // snapshot in users array
                users.append(user)
                print("👻\(document.documentID) => \(document.data())")
            }
        } catch {
            print("🔥Error getting documents: \(error)")
        }
    }
    
    // add data
    func addData(user: UserModel) async {
        do {
            var user = user
            let documentReference = try await db.collection("users").addDocument(data: user.toDictionary())
            user.id = documentReference.documentID
            users.append(user)
        } catch let error {
            print("Error adding document: \(error)")
        }
    }
    
    // delete data
    func deleteData(id: String) async {
        do {
            // Delete the document from Firestore
            try await db.collection("users").document(id).delete()
            
            // Remove the user from the users array
            if let index = users.firstIndex(where: { $0.id == id }) {
                users.remove(at: index)
            }
        } catch {
            print("Error deleting document: \(error)")
        }
    }
}

🚀やってみたこと

View側の構造体で、追加・表示・削除の処理を書いたが表示が変だった。画面が更新されていなかったり、ビルドしたらFirestoreから取得した値を表示できていなかった。ボタンを押すときに処理を実行するのですが、非同期処理なので、Task {}で囲んであげる必要がありました。

[保存の処理]
追加だと、awaitするのでTaskで囲む。

TextField("Name", text: $name)
                .padding()
                .border(Color.gray, width: 0.5)
            
            Button(action: {
                Task {
                    let user = UserModel(id: UUID().uuidString, name: name)
                    await userRepository.addData(user: user)
                }
            }) {
                Text("Add User")
            }
            .padding()

[削除の処理]
addと同じように、Task {}で処理を囲む。

Button(action: {
        Task {
            await userRepository.deleteData(id: user.id)
        }
    }) {
        Image(systemName: "trash")
    }

[表示の処理]
ページが呼ばれた時に、処理を実行するライフサイクルが必要でした。onAppearの中に、Firestoreのデータを取得する処理を書くことで解決できました。

List(userRepository.users, id: \.id) { user in
                HStack {
                    Text(user.name)
                    Spacer()
                    Button(action: {
                        Task {
                            await userRepository.deleteData(id: user.id)
                        }
                    }) {
                        Image(systemName: "trash")
                    }
                }
            }
        }.onAppear {
            Task {
                // ページが表示されたときにデータを取得
                await userRepository.getData()
            }
        }

[Viewの全体のコード]

import SwiftUI

struct ContentView: View {
    // input value
    @State private var name: String = ""
    
    // UserRepository instance
    @ObservedObject private var userRepository = UserRepository()
    
    var body: some View {
        VStack {
            TextField("Name", text: $name)
                .padding()
                .border(Color.gray, width: 0.5)
            
            Button(action: {
                Task {
                    let user = UserModel(id: UUID().uuidString, name: name)
                    await userRepository.addData(user: user)
                }
            }) {
                Text("Add User")
            }
            .padding()
            
            List(userRepository.users, id: \.id) { user in
                HStack {
                    Text(user.name)
                    Spacer()
                    Button(action: {
                        Task {
                            await userRepository.deleteData(id: user.id)
                        }
                    }) {
                        Image(systemName: "trash")
                    }
                }
            }
        }.onAppear {
            Task {
                // ページが表示されたときにデータを取得
                await userRepository.getData()
            }
        }
    }
}

こんな感じになります。

[追加・表示]

[削除]

🙂最後に

SwiftUIならではのライフサイクルの書き方までは、Firestoreの公式ドキュメントに書いていなかったのでハマりました💦
どのタイミングで処理が呼ばれるのか、画面が更新されるのかわかっていないと今回のように、裏側では処理が動いているけど、画面が変化しない問題が起きました🔥
この記事が誰かのお役に立てると思い書いてみました。

Jboy王国メディア

Discussion