diff --git a/README.md b/README.md index aefd3cf..b812615 100644 --- a/README.md +++ b/README.md @@ -131,16 +131,45 @@ The `Request` property wrapper allows you to make a network request and decode t It is inspired by SwiftData's @Query to provide the user with a familiar interface. ```swift - @Request(url: "https://jsonplaceholder.typicode.com/posts") - var posts: [Post]? - - @Request(url: "https://jsonplaceholder.typicode.com/posts/1") - var post: Post? + @Request( + client: SwiftyNetworkingClient(), + url: URL(string: "https://jsonplaceholder.typicode.com/posts"), + method: .get, + headers: ["Content-Type": "application/json"], + cachePolicy: .returnCacheDataElseLoad, + timeout: 10, + decoder: JSONDecoder() + ) + var response: Response<[Post]> ``` -The request is executed automatically as soon as the view is created, so you can directly access your object inside the body. -If there is an error, the object will be nil. +Each request is associated with a Response object which is trivially a three-state enum (loading, success and failure). + +The request is executed automatically as soon as the view is created, so you can directly access to the response inside the view body and show a different aspect of the view for each state through a simple switch. +```swift +List { + switch response { + case .loading: + LoadingCell() + case .success(let posts): + ForEach(posts, id: \.id) { post in + PostCell(post) + } + case .failure(let error): + ErrorCell(error) + } +} +``` +In case your request failed or you just want to update the result you can use its projectedValue to call the refresh method that will cause the view to be redrawn and the result to be updated. + +```swift +Button("Refresh") { + Task { + await $response.refresh() + } +} +``` --- # Support Your generous donations help sustain and improve this project. Here's why supporting us is important: diff --git a/Sources/SwiftyNetworking/Enums/Response.swift b/Sources/SwiftyNetworking/Enums/Response.swift new file mode 100644 index 0000000..482dd79 --- /dev/null +++ b/Sources/SwiftyNetworking/Enums/Response.swift @@ -0,0 +1,14 @@ +// +// Response.swift +// SwiftyNetworking +// +// Created by Antonio Guerra on 19/09/24. +// + +import Foundation + +public enum Response { + case loading + case success(Model) + case failure(Error) +} diff --git a/Sources/SwiftyNetworking/PropertyWrappers/Request.swift b/Sources/SwiftyNetworking/PropertyWrappers/Request.swift index f3f3d76..2e2d598 100644 --- a/Sources/SwiftyNetworking/PropertyWrappers/Request.swift +++ b/Sources/SwiftyNetworking/PropertyWrappers/Request.swift @@ -10,10 +10,9 @@ import Foundation import SwiftUI @propertyWrapper -public struct Request: DynamicProperty { +public struct Request: DynamicProperty, Refreshable { public typealias Method = SwiftyNetworkingRequest.Method - @State private var model: Model? = nil - @State private var error: Error? = nil + @State private var response: Response = .loading @State private var fetching: Bool = false let client: SwiftyNetworkingClient @@ -69,17 +68,25 @@ public struct Request: DynamicProperty { ) } - public var wrappedValue: Model? { - model + public var wrappedValue: Response { + response } - + + public var projectedValue: Refreshable { + self + } + private var shouldUpdate: Bool { - model == nil && error == nil && fetching == false + switch response { + case .loading: !fetching + default: false + } } @MainActor func fetch() async { do { + self.response = .loading self.fetching = true let request = try SwiftyNetworkingRequest( url: url, @@ -89,10 +96,11 @@ public struct Request: DynamicProperty { cachePolicy: cachePolicy, timeout: timeout ) - self.model = try await client.send(request, decoding: Model.self, using: decoder) + let model = try await client.send(request, decoding: Model.self, using: decoder) + self.response = .success(model) self.fetching = false } catch { - self.error = error + self.response = .failure(error) self.fetching = false } } @@ -103,9 +111,11 @@ public struct Request: DynamicProperty { } Task { - self.model = nil - self.error = nil await fetch() } } + + public func refresh() async { + await fetch() + } } diff --git a/Sources/SwiftyNetworking/Protocols/Refreshable.swift b/Sources/SwiftyNetworking/Protocols/Refreshable.swift new file mode 100644 index 0000000..886cdc8 --- /dev/null +++ b/Sources/SwiftyNetworking/Protocols/Refreshable.swift @@ -0,0 +1,10 @@ +// +// Refreshable.swift +// SwiftyNetworking +// +// Created by Antonio Guerra on 19/09/24. +// + +public protocol Refreshable { + func refresh() async +} diff --git a/Tests/SwiftyNetworkingTests/PropertyWrappers/RequestTests.swift b/Tests/SwiftyNetworkingTests/PropertyWrappers/RequestTests.swift deleted file mode 100644 index 3f5d357..0000000 --- a/Tests/SwiftyNetworkingTests/PropertyWrappers/RequestTests.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// RequestTests.swift -// -// -// Created by Antonio Guerra on 18/09/24. -// - -import SwiftyNetworking -import XCTest - -final class RequestTests: XCTestCase { - - func testInitFromURL() { - @Request(url: URL(string: "https://jsonplaceholder.typicode.com/users")) - var users: [JsonPlaceholderUser]? - XCTAssertNil(users) - } - - func testInitFromString() { - @Request(url: "https://jsonplaceholder.typicode.com/users") - var users: [JsonPlaceholderUser]? - XCTAssertNil(users) - } -}