🪶

SwiftUI を使用して Firestore からデータを取得する

2024/05/04に公開

対象者

  1. SwiftUIの経験がある人
  2. Firestoreの知識がある人
  3. 簡単なコードでデータフェッチを理解したい人

プロジェクトの説明

SwiftUIからFirestoreのデータを取得するのをなん度もやったことあるが、Swiftのコードに慣れていないので難しく感じる。SwiftUIだと、コードの書き方がそもそも違う。できるだけ多すぎないコードを書いてデータの取得をやってみました。

⌚️TODO

  1. 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()
}
  1. 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をモデルを格納する配列に追加する必要があります。
Jboy王国メディア

Discussion