Home > Back-end >  Async Property in SwiftUI List
Async Property in SwiftUI List

Time:10-19

I am trying to fetch all the replies for a particular Tweet. I am using Firestore to persist the tweets, replies etc. Below is the replies property inside the Tweet struct. I always checked and the compactMap function does return an array of Tweets.

var replies: [Tweet] {
        get async {
            let documents = try? await documentRef?.collection("replies").getDocuments().documents
            if let documents {
                // I checked and it does return replies as I can print them on the terminal
                return documents.compactMap { doc in
                    return try? doc.data(as: Tweet.self)
                }
            }
            return [] 
        }
    }

But inside the SwiftUI, when I assign and print out the self.replies property it comes as an empty array.

struct TweetDetailView: View {
    
    let tweet: Tweet
    @State private var replies: [Tweet] = []
    
    init(tweet: Tweet) {
        self.tweet = tweet
        setup()
    }
    
    private func setup() {
        Task {
            self.replies = await self.tweet.replies
            print(self.replies) // This prints an empty array WHY
            
        }
    }

Any ideas?

CodePudding user response:

The issue in your example doesn't actually have anything to do with the async code -- it has to do with how @State is set. For example, this code, without anything async, suffers from the same "issue":

struct ContentView: View {
    @State private var count = 0
    
    init() {
        launchTask()
    }
    
    func launchTask() {
        let incremented = self.count   1
        self.count = incremented
        print(self.count)
    }
    
    var body: some View {
        VStack {
            Text("hello, world")
        }
        .frame(width: 500, height: 500)
    }
}

But, there are a couple things going wrong here:

  1. Most directly, you cannot and should not depend on @State changing is value immediately after setting it -- it will be set on the next render loop. For example, if you print incremented in my example, it'll be the value one expects. In your example, you could do:
var result = await self.tweet.replies
print(result)
self.replies = result 

And, it too, will print the expect result (assuming your debugging is right and the async getter is indeed returning results).

  1. The second issue, although it doesn't relate directly to your problem, is that you should never do any 'work' in init in a View in SwiftUI. Instead, use the .task { ... } or .onAppear { ... } modifiers to launch the Task. Otherwise, your function will run on every init of the View, which could be many many times.
  • Related