Reputation: 77
I'm trying to implement a friendship model with firebase and swiftui. In one of my views, on its appear call, I call to check a friendship status method as shown below:
Edit: I've made the isFriend a published variable that should get updated like the following
class UserViewModel : ObservableObject{
....
//check friendship variabe
@Published var isFriend = 0
func checkFriendRequest(otherUserUID: String) -> Int{
//0 if not found in friend list or friend request
//1 means theyre friends
//2 means that user sent self/me a friend request
print("otherUserUID is \(otherUserUID)")
var pendingFriendRequests: [String: Bool] = [:]
self.ref.collection("Users").document(self.uid).getDocument(){
(document, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
pendingFriendRequests = document!.data()!["friends"] as! [String : Bool]
print("pendingFriendRequests is \(pendingFriendRequests)")
for key in pendingFriendRequests.keys {
if key == otherUserUID{
print("key is \(key) and value is \(pendingFriendRequests[key])")
if pendingFriendRequests[key] == true {
self.isFriend = 1
}else if pendingFriendRequests[key] == false {
self.isFriend = 2
}
}
}
}
}
return self.isFriend
}
}
In my view code, I would like to check the return value of this function and update the text/action of a button that should be on the view and it would look like the following:
struct OtherUser: View {
@StateObject var userData = UserViewModel()
var otherUserUID: String
var otherUserName: String
var otherUserUsername: String
var otherUserPic: String
@Environment(\.presentationMode) var present
init(_ otherUserUID: String, _ otherUserName: String, _ otherUserUsername: String, _ otherUserPic: String){
self.otherUserUID = otherUserUID
self.otherUserName = otherUserName
self.otherUserUsername = otherUserUsername
self.otherUserPic = otherUserPic
}
var body: some View {
ZStack(alignment: .top){
Color("Black")
.ignoresSafeArea()
VStack {
if (userData.checkFriendRequest(otherUserUID: otherUserUID)) == 1 {
//button for if returned 1
}else if (userData.checkFriendRequest(otherUserUID: otherUserUID)) == 2 {
//button for if returned 2
}else {
}
}
}
}
}
However, when the view appears it automatically returns 0 without updating the value after the async firebase call. I've looked up other solutions and tried dispatch groups but they don't seem to do the trick. I understand that it has something to do with the async nature of firebase but would like some assistance to see how to improve this so that the variable isFriend gets updated and then returned before the view appears. Thanks for your help
Upvotes: 0
Views: 1044
Reputation: 817
SwiftUI views automatically trigger updates according to the state of their views. Because the Firebase calls are asynchronous, you cannot expect that a synchronous call to the fetch function will return with the correct result.
Instead, you need to update a Published value (on the main thread) that will then broadcast an update message to all subscribers of that value. SwiftUI automatically handles the subscribe mechanism with StateObject or ObservedObject and triggers a new request for the body of the view.
To get your code working with SwiftUI, adjust checkFriendRequest as follows:
Change this:
if pendingFriendRequests[key] == true {
self.isFriend = 1
} else if pendingFriendRequests[key] == false {
self.isFriend = 2
}
To: (Because it triggers UI events and all UI must run on main thread)
DispatchQueue.main.async {
if pendingFriendRequests[key] == true {
self.isFriend = 1
} else if pendingFriendRequests[key] == false {
self.isFriend = 2
}
}
In your view, change the VStack to:
VStack {
if userData.isFriend == 1 {
//button for if returned 1
} else if userData.isFriend == 2 {
//button for if returned 2
} else {
}
}.onAppear() {
userData.checkFriendRequest(otherUserUID: otherUserUID)
}
Upvotes: 1