skip to Main Content

Once a user logs in to my app, I want to change the view from SignInView to AppShellView. The authData variable updates perfectly fine in UserViewModel once it receives data from the API (checked in UserViewModel via print statement) but this update is not being reflected in ContentView as it always shows the SignInView.

Why is authData not being updated in ContentView?

UserViewModel:

import SwiftUI

struct LoginRequestBody: Codable {
    let email: String
    let password: String
}

struct AuthData: Codable {
    let token: String
    let loggedInUser: User
}

class UserViewModel: ObservableObject {
    // MARK: - PROPERTIES

    @Published var authData: AuthData?
    
    // MARK: - FUNCTIONS
    
    func signIn(email: String, password: String) {
        guard let url = URL(string: "http://localhost:4000/api/users/login") else {
            return
        }
        
        let body = LoginRequestBody(email: email, password: password)
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONEncoder().encode(body)
        
        URLSession.shared.dataTask(with: request) { [self] data, response, error in
            guard let data = data, error == nil else {
                return
            }

            guard let loginResponse = try? JSONDecoder().decode(AuthData.self, from: data) else {
                return
            }
            
            self.authData = loginResponse
            
            if let encoded = try? JSONEncoder().encode(self.authData) {
                let defaults = UserDefaults.standard
                defaults.set(encoded, forKey: "authData")
            }
        }.resume()
    }
}

ContentView:

import SwiftUI

struct ContentView: View {
    @ObservedObject var userViewModel = UserViewModel()
    
    var body: some View {
        if userViewModel.authData != nil {
            AppShellView()
        } else {
            SignInView()
        }
    }
}

Edit:
The problem seems to be the following: While the code successfully creates the authData object in UserViewModel, it cannot share its updated state outside of UserViewModel (despite being declared as @Published) no matter in which other struct or view I try to use it.

2

Answers


  1. It is possible that the code execution may exit the URLSession request completion handler in the following two places without updating ‘userViewModel.authData’.

    guard let data = data, error == nil else {
        return
    }
    
    guard let loginResponse = try? JSONDecoder().decode(AuthData.self, from: data) else {
        return
    }
    

    To evaluate this hypothesis, add print or debugger breakpoint.

    Additionally, the URLSession request completion handler executes the code on a non-main thread, so the line

    self.authData = loginResponse
    

    should be wrapped by DispatchQueue.main.async.

    Login or Signup to reply.
  2. It’s possible that the bug resides here in the else clause:

    guard let data = data, error == nil else {
        return
    }
    

    as it passes through it due to the url your are using is in http which leads to an error of:

    The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

    you can solve it by Using HTTPS instead or add this domain to Exception Domains in your Info.plist.

    Note that you need to wrap this part self.authData = loginResponse on DispatchQueue.main.async {} as you are making UI change from a background thread.

    Hope this helps , good luck!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search