Search code examples
asynchronousswiftuipublisher

How to run async function (URLSession) inside a Publisher .onReceive


What would be the proper way to avoid Task {} inside a .onReceive() closure and still be able to run async code?

If I add a Task {} everything runs as it should but shouldn't I replace all Task {} blocks in swiftui? Whenever possible I'm using .task directly on the view but in this case I need to run an URLSession from a publisher onReceive closure.


Solution

  • You can have the onReceive alter a variable that serves as an id for task.

    struct TestURLSessionOnReceive: View {
        //Object from the publisher/task id
        @State private var id: UUID?
        //Result from async task
        @State private var progress: String?
        var body: some View {
            VStack{
                Button("post notification") { //Mocking a publisher trigger
                    NotificationCenter.default.post(name: .someNotification, object: UUID.init().uuidString)
                }
                if let progress { //Mocking a result from an async task
                    ProgressView {
                        Text(progress)
                    }
                }
            }
            //Listen for the publisher
            .onReceive(NotificationCenter.default.publisher(for: .someNotification), perform: { notification in
                guard let uuidString = notification.object as? String, let uuid = UUID(uuidString: uuidString) else {
                    return
                }
                self.id = uuid
            })
            //Complete a task based on a publisher
            .task(id: id) {
                guard let id else {return} //Ignore the "onAppear" call
                progress = "Working on\nid = \(id)"
                //Do any async await code
                
                //let (data, response) = try await URLSession.shared.data(from: <#T##URL#>)
                try? await Task.sleep(for: .seconds(2)) //Mocking async task
                
                progress = nil
            }
        }
    }
    
    extension Notification.Name {
    
        static let someNotification = Notification.Name("someNotification")
    }