0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

えーえすAdvent Calendar 2024

Day 3

SwiftUIで@FirestoreQueryを上手に使おう - collectionPathを変更する方法 -

Last updated at Posted at 2024-12-02

はじめに

@FirestoreQueryとは、以下のようなやつです。

FirebaseQuery
@FirestoreQuery(collectionPath: "users") var users: [User]

こうやって書くだけで自動でFirestoreからusersコレクションのデータを撮ってきてくれる!しかも勝手にUser型に変換してくれる!!嬉しい!!

と、とても便利なはずの@FirestoreQuery君なのですが、あまり使われてる場面や記事を見かけません。なぜ使いにくいのか、そして使いやすくなる方法を考えたので、ぜひ読んでみてください。

@FirestoreQueryでできること

前述の通り、書くだけでコレクションのデータ取得・変化の監視をすることができます。

このとき、UserCodableに準拠したstructである必要があります。

User
import FirebaseFirestore

struct User: Identifiable, Codable {
    @DocumentID var id: String?
    var name: String
    var email: String
}

ここで、@DocumentIDをつけると勝手にドキュメントのIDを取得してくれます!(RealtimeDatabaseの頃は手動だったから大変だった…良い時代になりました。)

余談:そもそもFirestoreとCodableの相性がすこぶる良いです。

@FirestoreQueryの残念なこと

Pathに変数を含められない

これが一番残念な点。例えばNavigationLinkで画面遷移するとします。

ここでUserの各々のドキュメントの中にscoresコレクションがあったとします。

image.png

このscoresコレクションへのパスusers/{document_id}/scoresを**うまく定義できない。つまり、以下の書き方がエラーになります。

エラーになる
@FirestoreQuery(collectionPath: "users/\(userID)") var users: [User]
// Cannot use instance member 'path' within property initializer; property initializers run before 'self' is available

ドキュメント内のコレクションはうまくとってこれない

これは普通にFirestoreの悪いところですが、上のようにscoresを配置しても、[Score]として自動でDecodeしてくれません
無理やり辞書型でドキュメント内に記述してもいいですが、structとの対応などを考えるとあまり良くないですよね。

また、usersを監視しているだけではscoresを取得できないなどもあり、大変です。

@FirestoreQueryをうまく使おう

さて、本題として、重要なのはどうやってcollectionPathに変数を含めるかです。これがどうやっても記事が出てこなかった…

結論から言うと以下のように変更することができます。

pathの変更
.onAppear {
    $users.path = path
}

usersBindingとして扱ってあげる必要があり、しかもcollectionPathではなく単にpathと指定するようです。

上で作った、usersの中にscoresがあるものを例にすれば、

ContentView
import SwiftUI
import FirebaseFirestore

struct User: Identifiable, Codable {
    @DocumentID var id: String?
    var name: String
    var email: String    
}

struct ContentView: View {
    @FirestoreQuery(collectionPath: "users") var users: [User]
    
    var body: some View {
        NavigationStack {
            List(users) { user in
                NavigationLink {
                    NextView(path: $users.path + "/" + user.id! + "/scores")
                } label: {
                    VStack(alignment: .leading) {
                        Text(user.name)
                            .font(.headline)
                        Text(user.email)
                            .font(.subheadline)
                    }
                }
            }
        }
    }
}
NextView
import SwiftUI
import FirebaseFirestore

struct Score: Identifiable, Codable {
    @DocumentID var id: String?
    var score: Int
}

struct NextView: View {
    @FirestoreQuery(collectionPath: "empty") var scores: [Score]
    var path: String
    
    var body: some View {
        List(scores) { score in
            Text(String(score.score))
        }
        .onAppear {
            $scores.path = path
        }
    }
}

NextViewで、最初はcollectionPath"empty"とするのなどはアンチパターンかも知れませんが、とにかくこうすればうまくいく。というのが言いたかったことです。

まとめ

手軽にFirestoreの変更を監視できる上、getDocumentsなどを全く書かなくてもなんとかなるのは本当に手軽なので、なにかしら活用できればいいなと思ってこの記事を書きました。

ともあれ、.onAppearで変更するしかなかったり、アーキテクチャのことを考えると、Readは@FirestoreQueryでView内で行い、Create/Update/DeleteはFirestoreのレポジトリから行う、など分離するのが良くなかったりといった意見もあるかも知れません。この辺はベストプラクティスを探っていきたいなと思っています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?