msoni
msoni

Reputation: 25

How to publish changes from the background thread Swift UI GET Request

I have set up my app such that I use UserDefaults to store a users login info (isLoggedIn, account settings). If a user is logged in and exits out of the application, and then relaunches the app, I would like them to be returned to the home page tab.

This functionality works; however, for some reason, on relaunch the home page has a getRequest that should be carried out. Instead, the screen goes white. This request and the loading involved works when I navigate from the login, but not when I relaunch the app. I get this warning:

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

In looking at other stack overflow posts, the common sentiment seems to be to wrap any type of change in a dispatchqueue.main.async; however, this does not seem to work for me.

import SwiftUI

struct StoresView: View {
    @ObservedObject var request = Request()
    @Environment(\.imageCache) var cache: ImageCache
    @EnvironmentObject var carts: Carts

    init() {
        getStores()
    }

    var body: some View {

        NavigationView {
            List(self.request.stores) { store in
                NavigationLink(destination: CategoryHome(store: store).environmentObject(self.carts)) {
                    VStack(alignment: .leading) {
                        Text(store.storeName)
                            .font(.system(size: 20))
                    }
                }
            }.navigationBarTitle(Text("Stores").foregroundColor(Color.black))
        }
    }

    func getStores() {
        DispatchQueue.main.async {
            self.request.getStoresList() { stores, status in
                if stores != nil {
                    self.request.stores = stores!
                }
            }
        }
    }
}

get stores call in Request class

class Request: ObservableObject {
@Published var stores = [Store]()
let rest = RestManager()


func getStoresList(completionHandler: @escaping ([Store]?, Int?)-> Void) {
    guard let url = URL(string: "@@@@@@@@@@@@@@@@@@@") else { return }

    self.rest.makeRequest(toURL: url, withHttpMethod: .GET, useSessionCookie: false) { (results) in
        guard let response = results.response else { return }
        if response.httpStatusCode == 200 {
            guard let data = results.data else { return}
            let decoder = JSONDecoder()
            guard let stores = try? decoder.decode([Store].self, from: data) else { return }
            completionHandler(stores, response.httpStatusCode)
        } else {
            completionHandler(nil, response.httpStatusCode)
        }
    }

}

Make Request from RestManager, I included the make request because I've seen some others use shared dataPublishing tasks, but I may not have used it correctly when trying to use it. Any advice or help would be appreciated. Thanks!

func makeRequest(toURL url: URL,
                 withHttpMethod httpMethod: HttpMethod, useSessionCookie: Bool?,
                 completion: @escaping (_ result: Results) -> Void) {

    DispatchQueue.main.async { [weak self] in
        let targetURL = self?.addURLQueryParameters(toURL: url)
        let httpBody = self?.getHttpBody()
        // fetches cookies and puts in appropriate header and body attributes
        guard let request = self?.prepareRequest(withURL: targetURL, httpBody: httpBody, httpMethod: httpMethod, useSessionCookie: useSessionCookie) else
        {
            completion(Results(withError: CustomError.failedToCreateRequest))
            return
        }

        let sessionConfiguration = URLSessionConfiguration.default
        let session = URLSession(configuration: sessionConfiguration)

        let task = session.dataTask(with: request) { (data, response, error) in
            print(response)
            completion(Results(withData: data,
                               response: Response(fromURLResponse: response),
                               error: error))
        }
        task.resume()
    }
}

Upvotes: 1

Views: 3925

Answers (1)

Frankenstein
Frankenstein

Reputation: 16341

You seem to be trying to call the function in the Main tread instead of setting the stores property. Calling request. getStoresList is already in the main thread once the call is made you enter the background thread from there you need to come back to the main thread once the URLSession is complete. You need to make the UI modification in the Main thread instead of the background tread as the error clearly state. Here's what you need to do to fix this issue:

    func getStores() {
        self.request.getStoresList() { stores, status in
            DispatchQueue.main.async {
                if stores != nil {
                    self.request.stores = stores!
                }
            }
        }
    }

Upvotes: 3

Related Questions