Skip to content

RF-9688 - Remove ReactiveSwift from Workflow public interface #360

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ let package = Package(

.target(
name: "Workflow",
dependencies: ["ReactiveSwift"],
path: "Workflow/Sources"
),

.target(
name: "WorkflowTesting",
dependencies: [
Expand Down
5 changes: 4 additions & 1 deletion Samples/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ let project = Project(
.unitTest(
for: "Workflow",
sources: "../Workflow/Tests/**",
dependencies: [.external(name: "Workflow")]
dependencies: [
.external(name: "ReactiveSwift"),
.external(name: "Workflow"),
]
),
.unitTest(
for: "WorkflowTesting",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class RootWorkflowTests: XCTestCase {

// First rendering is just the welcome screen. Update the name.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(1, backStack.items.count)

guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -100,7 +100,7 @@ class RootWorkflowTests: XCTestCase {

// Log in and go to the todo list.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(1, backStack.items.count)

guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -113,7 +113,7 @@ class RootWorkflowTests: XCTestCase {

// Expect the todo list to be rendered. Edit the first todo.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(2, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -134,7 +134,7 @@ class RootWorkflowTests: XCTestCase {

// Selected a todo to edit. Expect the todo edit screen.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(3, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -158,7 +158,7 @@ class RootWorkflowTests: XCTestCase {

// Save the selected todo.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(3, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand Down Expand Up @@ -204,7 +204,7 @@ class RootWorkflowTests: XCTestCase {

// Expect the todo list. Validate the title was updated.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(2, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand Down
38 changes: 25 additions & 13 deletions Workflow/Sources/WorkflowHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import ReactiveSwift
import Combine

/// Defines a type that receives debug information about a running workflow hierarchy.
public protocol WorkflowDebugger {
Expand All @@ -30,18 +30,30 @@ public protocol WorkflowDebugger {
func didUpdate(snapshot: WorkflowHierarchyDebugSnapshot, updateInfo: WorkflowUpdateDebugInfo)
}

/// Manages an active workflow hierarchy.
public final class WorkflowHost<WorkflowType: Workflow> {
private let (outputEvent, outputEventObserver) = Signal<WorkflowType.Output, Never>.pipe()
public protocol WorkflowOutputPublisher {
associatedtype Output

var outputPublisher: AnyPublisher<Output, Never> { get }
}
Comment on lines +33 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this protocol necessary? what's the benefit over just extending the concrete type directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows us to add the output signal on both WorkflowHost and WorkflowHostingController by just extending the protocol in WorkflowReactiveSwift. This was a suggestion in Andrew's feedback.


/// Manages an active workflow hierarchy.
public final class WorkflowHost<WorkflowType: Workflow>: WorkflowOutputPublisher {
// @testable
let rootNode: WorkflowNode<WorkflowType>

private let mutableRendering: MutableProperty<WorkflowType.Rendering>
private let renderingSubject: CurrentValueSubject<WorkflowType.Rendering, Never>
private let outputSubject = PassthroughSubject<WorkflowType.Output, Never>()

/// Represents the `Rendering` produced by the root workflow in the hierarchy. New `Rendering` values are produced
/// as state transitions occur within the hierarchy.
public let rendering: Property<WorkflowType.Rendering>
public var rendering: WorkflowType.Rendering {
renderingSubject.value
}

/// A Publisher containing rendering events produced by the root workflow in the hierarchy.
public var renderingPublisher: AnyPublisher<WorkflowType.Rendering, Never> {
renderingSubject.eraseToAnyPublisher()
}

/// Context object to pass down to descendant nodes in the tree.
let context: HostContext
Expand Down Expand Up @@ -78,8 +90,8 @@ public final class WorkflowHost<WorkflowType: Workflow> {
parentSession: nil
)

self.mutableRendering = MutableProperty(rootNode.render())
self.rendering = Property(mutableRendering)
self.renderingSubject = CurrentValueSubject(rootNode.render())

rootNode.enableEvents()

debugger?.didEnterInitialState(snapshot: rootNode.makeDebugSnapshot())
Expand Down Expand Up @@ -110,12 +122,12 @@ public final class WorkflowHost<WorkflowType: Workflow> {
private func handle(output: WorkflowNode<WorkflowType>.Output) {
let shouldRender = !shouldSkipRenderForOutput(output)
if shouldRender {
mutableRendering.value = rootNode.render()
renderingSubject.send(rootNode.render())
}

// Always emit an output, regardless of whether a render occurs
if let outputEvent = output.outputEvent {
outputEventObserver.send(value: outputEvent)
outputSubject.send(outputEvent)
}

debugger?.didUpdate(
Expand All @@ -129,9 +141,9 @@ public final class WorkflowHost<WorkflowType: Workflow> {
}
}

/// A signal containing output events emitted by the root workflow in the hierarchy.
public var output: Signal<WorkflowType.Output, Never> {
outputEvent
/// A publisher containing output events emitted by the root workflow in the hierarchy.
public var outputPublisher: AnyPublisher<WorkflowType.Output, Never> {
outputSubject.eraseToAnyPublisher()
}
}

Expand Down
8 changes: 5 additions & 3 deletions Workflow/Tests/AnyWorkflowTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import ReactiveSwift
import Combine
import XCTest
@testable import Workflow

Expand All @@ -40,19 +40,21 @@ public class AnyWorkflowTests: XCTestCase {
let host = WorkflowHost(workflow: OnOutputWorkflow())

let renderingExpectation = expectation(description: "Waiting for rendering")
host.rendering.producer.startWithValues { rendering in
let cancellable = host.renderingPublisher.sink { rendering in
if rendering {
renderingExpectation.fulfill()
}
}

let outputExpectation = expectation(description: "Waiting for output")
host.output.observeValues { output in
let outputCancellable = host.outputPublisher.sink { output in
if output {
outputExpectation.fulfill()
}
}
wait(for: [renderingExpectation, outputExpectation], timeout: 1)
cancellable.cancel()
outputCancellable.cancel()
}

func testOnlyWrapsOnce() {
Expand Down
Loading
Loading