🪶
SwiftUI を使用して Firestore からデータを取得する
対象者
- SwiftUIの経験がある人
- Firestoreの知識がある人
- 簡単なコードでデータフェッチを理解したい人
プロジェクトの説明
SwiftUIからFirestoreのデータを取得するのをなん度もやったことあるが、Swiftのコードに慣れていないので難しく感じる。SwiftUIだと、コードの書き方がそもそも違う。できるだけ多すぎないコードを書いてデータの取得をやってみました。
⌚️TODO
- Firestoreの構造に合わせて構造体を作成する。作らなくてもいいけど、Firestoreのインスタンスを生成したプロパティが使えるクラスを作る。これを継承して今回はプログラムを作りました。
[Firestore Model]
Structure created for Firestore collection
/// [Firestore Data Model]
/// `Codable` is a type alias for the `Encodable` and `Decodable` protocols.
struct UserModel: Codable {
var id: String
var name: String
}
[Base Class]
Super class that creates Firestore instances
// super class
class BaseRepository {
// instance of Firestore
let db = Firestore.firestore()
}
- SwiftUIでは、ObservableObjectに準拠したクラスでないと、View側でデータフェッチをすることができない。他の言語でもあると思うが、非同期の処理を使うためのルールなのだろう。もしプロトコルに準拠していないとエラーが発生する🔥
👎bad code
Simply using an instantiated class will result in an error.
[repository]
import FirebaseFirestore
// sub class
class UserRepository : BaseRepository {
// fetch user data from Firestore
func getData() async {
do {
// users array
var users = [UserModel]()
// snaphot propaty
let snapshot = try await db.collection("users").getDocuments()
// snapshot for loop
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)")
}
}
}
[view]
import SwiftUI
struct ContentView: View {
// UserRepository instance
@ObservedObject private var userRepository = UserRepository()
var body: some View {
VStack {
List(userRepository.users, id: \.id) { user in
Text(user.name)
}
.task {
await userRepository.getData()
}
}
}
}
👍Good Code
The problem seems to be that it doesn't conform to the ObservableObject protocol. You should also use the @Published property wrapper to notify the View every time you update the users array.
[repository]
import FirebaseFirestore
import Combine
class UserRepository : BaseRepository, ObservableObject {
// fetch user data from Firestore
@Published var users = [UserModel]()
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)")
}
}
}
[view]
import SwiftUI
struct ContentView: View {
// UserRepository instance
@ObservedObject private var userRepository = UserRepository()
var body: some View {
VStack {
List(userRepository.users, id: \.id) { user in
Text(user.name)
}
.task {
await userRepository.getData()
}
}
}
}
感想
ビジネスロジックを書いたクラスをインスタンス化して、データフェッチして、表示できるかと思いきゃ SwiftUIならではのプロトコルに準拠させたり、データを保持するモデルを格納した配列にアノテーションをつけなければならなことでハマった😅
できるだけ短いコードで書いたので誰かの学習のお役に立ってくれると嬉しいです。
- SwiftUI で Firestore からデータを取得するには、ロジックを記述したクラスが ObservableObject に準拠している必要があります。
- @Publishedをモデルを格納する配列に追加する必要があります。
Discussion