Skip to content

Try adding Combine again #396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Try adding Combine again
  • Loading branch information
jkmassel committed Nov 27, 2024
commit 18a615f931c290b52cea8d9604db473e526da854
4 changes: 4 additions & 0 deletions native/swift/Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
242132C82CE69CE80021D8E8 /* WordPressAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 242132C72CE69CE80021D8E8 /* WordPressAPI */; };
242132CB2CE7D0BE0021D8E8 /* Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242132C92CE7D0220021D8E8 /* Combine.swift */; };
242D648E2C3602C1007CA96C /* ListViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242D648D2C3602C1007CA96C /* ListViewData.swift */; };
242D64922C360687007CA96C /* RootListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242D64912C360687007CA96C /* RootListView.swift */; };
242D64942C3608C6007CA96C /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242D64932C3608C6007CA96C /* ListView.swift */; };
Expand All @@ -23,6 +24,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
242132C92CE7D0220021D8E8 /* Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Combine.swift; sourceTree = "<group>"; };
242D648D2C3602C1007CA96C /* ListViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewData.swift; sourceTree = "<group>"; };
242D64912C360687007CA96C /* RootListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootListView.swift; sourceTree = "<group>"; };
242D64932C3608C6007CA96C /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -90,6 +92,7 @@
2479BF7F2B621CB60014A01D /* Example */ = {
isa = PBXGroup;
children = (
242132C92CE7D0220021D8E8 /* Combine.swift */,
242D64972C363960007CA96C /* UI */,
242D64982C363996007CA96C /* Resources */,
2479BF802B621CB60014A01D /* ExampleApp.swift */,
Expand Down Expand Up @@ -187,6 +190,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
242132CB2CE7D0BE0021D8E8 /* Combine.swift in Sources */,
242D64922C360687007CA96C /* RootListView.swift in Sources */,
2479BF812B621CB60014A01D /* ExampleApp.swift in Sources */,
24A3C32F2BA8F96F00162AD1 /* LoginView.swift in Sources */,
Expand Down
58 changes: 58 additions & 0 deletions native/swift/Example/Example/Combine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Foundation
import Combine
import SwiftUI
import WordPressAPI

@Observable class CombineListViewModel: ListViewModel {

public typealias StreamProvider = () throws -> ListViewDataStream

var listItems: [String: ListViewData] = [:]
var isLoading: Bool = false

var shouldPresentAlert: Bool = false

var error: MyError?
var cancellables: Set<AnyCancellable> = []

private let streamProvider: StreamProvider

init(streamProvider: @escaping StreamProvider) {
self.streamProvider = streamProvider
}

func task() async {
self.error = nil

do {
let currentStream = try self.streamProvider()

currentStream.publisher
.sink { completion in
switch completion {
case .finished:
self.isLoading = false

case .failure(let error):
self.error = MyError(underlyingError: error)
self.shouldPresentAlert = true
}
} receiveValue: { newValue in
withAnimation {
for item in newValue {
self.listItems[item.id] = item
}
}
}
.store(in: &cancellables)

} catch {
self.error = MyError(underlyingError: error)
self.shouldPresentAlert = true
}
}
}

struct ListViewDataStream {
let publisher: AnyPublisher<[ListViewData], Error>
}
7 changes: 7 additions & 0 deletions native/swift/Example/Example/ExampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ struct ExampleApp: App {
let sequence = try WordPressAPI.globalInstance.posts.sequenceWithEditContext(params: postListParams)
return ListViewSequence(underlyingSequence: sequence)
}),
RootListData(name: "Posts with Combine", publisher: {
let publisher = try WordPressAPI.globalInstance.posts.publisherWithEditContext(params: postListParams).tryMap {
$0.data.map(\.asListViewData)
}.eraseToAnyPublisher()

return ListViewDataStream(publisher: publisher)
}),
RootListData(name: "Media", sequence: {
let sequence = try WordPressAPI.globalInstance.media.sequenceWithEditContext(params: mediaListParams)
return ListViewSequence(underlyingSequence: sequence)
Expand Down
17 changes: 17 additions & 0 deletions native/swift/Example/Example/UI/RootListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ struct RootListViewItem: View {
Text(name)
}
}

case .publisher(let name, let streamProvider):
VStack(alignment: .leading, spacing: 4.0) {
NavigationLink {
ListView(
viewModel: CombineListViewModel(streamProvider: streamProvider)
)
} label: {
Text(name)
}
}
}
}
}
Expand All @@ -47,11 +58,13 @@ enum RootListData: Identifiable {

case callback(String, TaskListViewModel.FetchDataTask)
case sequence(String, SequenceListViewModel.SequenceProvider)
case publisher(String, CombineListViewModel.StreamProvider)

var id: String {
switch self {
case .callback(let id, _): id
case .sequence(let id, _): id
case .publisher(let id, _): id
}
}

Expand All @@ -62,6 +75,10 @@ enum RootListData: Identifiable {
init(name: String, sequence: @escaping SequenceListViewModel.SequenceProvider) {
self = .sequence(name, sequence)
}

init(name: String, publisher: @escaping CombineListViewModel.StreamProvider) {
self = .publisher(name, publisher)
}
}

#Preview {
Expand Down
85 changes: 85 additions & 0 deletions native/swift/Sources/wordpress-api/Combine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Foundation
import WordPressAPIInternal
import Combine

public protocol PublisherAwareExecutor: PaginationAwareExecutor {

var requestBuilder: PublisherRequestBuilder<Self> { get }
var parser: ResponseParser<Self> { get }

// Generated implementation
func publisherWithEditContext(
params: EditContextResponseType.ParamsType
) -> AnyPublisher<EditContextResponseType, Error>

func publisherWithViewContext(
params: ViewContextResponseType.ParamsType
) -> AnyPublisher<ViewContextResponseType, Error>

func publisherWithEmbedContext(
params: EmbedContextResponseType.ParamsType
) -> AnyPublisher<EmbedContextResponseType, Error>
}

public struct ResponseParser<T> where T: PublisherAwareExecutor {
let editContextParser: (WpNetworkResponse) throws -> T.EditContextResponseType
let viewContextParser: (WpNetworkResponse) throws -> T.ViewContextResponseType
let embedContextParser: (WpNetworkResponse) throws -> T.EmbedContextResponseType
}

public struct PublisherRequestBuilder<T> where T: PublisherAwareExecutor {
let editContextBuilder: (T.EditContextResponseType.ParamsType) -> WpNetworkRequest
let viewContextBuilder: (T.ViewContextResponseType.ParamsType) -> WpNetworkRequest
let embedContextBuilder: (T.EmbedContextResponseType.ParamsType) -> WpNetworkRequest
}

extension PublisherAwareExecutor {
func perform(request: WpNetworkRequest) -> Publishers.TryMap<URLSession.DataTaskPublisher, WpNetworkResponse> {
URLSession.shared.dataTaskPublisher(for: request.asURLRequest())
.tryMap { try WpNetworkResponse.from(data: $0.data, response: $0.response) }
}

public func publisherWithEditContext(
params: EditContextResponseType.ParamsType
) -> AnyPublisher<EditContextResponseType, Error> {
perform(request: self.requestBuilder.editContextBuilder(params))
.tryMap { try parser.editContextParser($0) }
.eraseToAnyPublisher()
}

public func publisherWithViewContext(
params: ViewContextResponseType.ParamsType
) -> AnyPublisher<ViewContextResponseType, Error> {
perform(request: self.requestBuilder.viewContextBuilder(params))
.tryMap { try parser.viewContextParser($0) }
.eraseToAnyPublisher()
}

public func publisherWithEmbedContext(
params: EmbedContextResponseType.ParamsType
) -> AnyPublisher<EmbedContextResponseType, Error> {
perform(request: self.requestBuilder.embedContextBuilder(params))
.tryMap { try parser.embedContextParser($0) }
.eraseToAnyPublisher()
}
}

extension PostsRequestExecutor: PublisherAwareExecutor {
public var requestBuilder: PublisherRequestBuilder<WordPressAPIInternal.PostsRequestExecutor> {
PublisherRequestBuilder { params in
PostsRequestBuilder(noPointer: .init()).listWithEditContext(params: params)
} viewContextBuilder: { params in
PostsRequestBuilder(noPointer: .init()).listWithViewContext(params: params)
} embedContextBuilder: { params in
PostsRequestBuilder(noPointer: .init()).listWithEmbedContext(params: params)
}
}

public var parser: ResponseParser<WordPressAPIInternal.PostsRequestExecutor> {
ResponseParser(
editContextParser: parseAsPostsRequestListWithEditContextResponse,
viewContextParser: parseAsPostsRequestListWithViewContextResponse,
embedContextParser: parseAsPostsRequestListWithEmbedContextResponse
)
}
}
24 changes: 15 additions & 9 deletions native/swift/Sources/wordpress-api/WordPressAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public struct WordPressAPI {
}

private let urlSession: URLSession
package let requestBuilder: UniffiWpApiClient
package let internalClient: UniffiWpApiClient
package let requestBuilder: UniffiWpApiRequestBuilder

public init(urlSession: URLSession, baseUrl: ParsedUrl, authenticationStategy: WpAuthentication) {
self.init(
Expand All @@ -32,43 +33,48 @@ public struct WordPressAPI {
executor: SafeRequestExecutor
) {
self.urlSession = urlSession
self.requestBuilder = UniffiWpApiClient(
self.internalClient = UniffiWpApiClient(
siteUrl: baseUrl,
authentication: authenticationStategy,
requestExecutor: executor
)

self.requestBuilder = UniffiWpApiRequestBuilder(
siteUrl: baseUrl,
authentication: authenticationStategy
)
}

public var users: UsersRequestExecutor {
self.requestBuilder.users()
self.internalClient.users()
}

public var plugins: PluginsRequestExecutor {
self.requestBuilder.plugins()
self.internalClient.plugins()
}

public var applicationPasswords: ApplicationPasswordsRequestExecutor {
self.requestBuilder.applicationPasswords()
self.internalClient.applicationPasswords()
}

public var siteHealthTests: WpSiteHealthTestsRequestExecutor {
self.requestBuilder.wpSiteHealthTests()
self.internalClient.wpSiteHealthTests()
}

public var postTypes: PostTypesRequestExecutor {
self.requestBuilder.postTypes()
self.internalClient.postTypes()
}

public var posts: PostsRequestExecutor {
self.requestBuilder.posts()
self.internalClient.posts()
}

public var media: MediaRequestExecutor {
self.requestBuilder.media()
}

public var siteSettings: SiteSettingsRequestExecutor {
self.requestBuilder.siteSettings()
self.internalClient.siteSettings()
}

package func perform(request: WpNetworkRequest) async throws -> WpNetworkResponse {
Expand Down