0

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

5
  • "...gets updated and then returned before the view appears" What you're asking is not possible. What if the network is slow or not connected? The view should hang/block? The best you can do is update a \@State or \@ObservedObject that (automatically) triggers the view to update itself with new results. You don't show your view code... Commented Jul 23, 2021 at 18:31
  • Thanks for the reply, I've edited my code to include the published/observed object but it still didn't work and is only returning 0. I've shown my view code that I would like to see changed based on if it returns 0, 1, or 2. Thanks Commented Jul 23, 2021 at 18:49
  • Firebase calls like getDocument() are asynchronous. You can't return a value at the end of the function. Instead of if (userData.checkFriendRequest) you should be using if userData.isFriend. Commented Jul 23, 2021 at 19:26
  • Where would I call the checkFriendRequest() function to update the isFriend variable then? Commented Jul 23, 2021 at 19:28
  • 1
    @HariKrishnaSenthilkumar traditionally, something like onAppear Commented Jul 23, 2021 at 19:47

1 Answer 1

1

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)
      }
Sign up to request clarification or add additional context in comments.

2 Comments

Check your if clauses for unmatched parenthesis.
removed unnecessary parentheses.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.