Skip to content

Commit

Permalink
Implement ObservedDependency
Browse files Browse the repository at this point in the history
  • Loading branch information
0xLeif committed Jan 24, 2024
1 parent a6bd9be commit 18bd22f
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 2 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ AppState is a Swift Package that simplifies the management of application state
### Dependency Management

- **Dependency:** Struct for encapsulating dependencies within the app's scope.
- 🍎 **ObservedDependency:** Struct for encapsulating dependencies within the app's scope. Backed by an `@ObservedObject` to publish changes to SwiftUI views.
- **Scope:** Represents a specific context within an app, defined by a unique name and ID.

### Property Wrappers
Expand Down Expand Up @@ -300,7 +301,7 @@ In this case, ContentView has access to the networkService dependency and can us

### Using Dependency with ObservableObject

When your dependency is an `ObservableObject`, any changes to it will automatically update your SwiftUI views. Make sure your service conforms to the `ObservableObject` protocol. To do this, you should not use the `@AppDependency` property wrapper, but instead use the `@ObservedObject` property wrapper.
When your dependency is an `ObservableObject`, any changes to it will automatically update your SwiftUI views. Make sure your service conforms to the `ObservableObject` protocol. To do this, you should not use the `@AppDependency` property wrapper, but instead use the `@ObservedDependency` property wrapper.

Here's an example:

Expand All @@ -318,7 +319,7 @@ extension Application {
}

struct ContentView: View {
@ObservedObject var dataService = Application.dependency(\.dataService)
@ObservedDependency(\.dataService) private var dataService

var body: some View {
List(dataService.data, id: \.self) { item in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#if !os(Linux) && !os(Windows)
import SwiftUI

/// The `@ObservedDependency` property wrapper is a feature provided by AppState, intended to simplify dependency handling throughout your application. It makes it easy to access, share, and manage dependencies in a neat and Swift idiomatic way. It works the same as `@AppDependency`, but comes with the power of the `@ObservedObject` property wrapper.
@propertyWrapper public struct ObservedDependency<Value>: DynamicProperty where Value: ObservableObject {
/// Path for accessing `ObservedDependency` from Application.
private let keyPath: KeyPath<Application, Application.Dependency<Value>>

@ObservedObject private var observedObject: Value

private let fileID: StaticString
private let function: StaticString
private let line: Int
private let column: Int

/// Represents the current value of the `ObservedDependency`.
public var wrappedValue: Value { observedObject }

/// A binding to the `ObservedDependency`'s value, which can be used with SwiftUI views.
public var projectedValue: ObservedObject<Value>.Wrapper { $observedObject }

/**
Initializes the AppDependency with a `keyPath` for accessing `Dependency` in Application.

- Parameter keyPath: The `KeyPath` for accessing `Dependency` in Application.
*/
public init(
_ keyPath: KeyPath<Application, Application.Dependency<Value>>,
_ fileID: StaticString = #fileID,
_ function: StaticString = #function,
_ line: Int = #line,
_ column: Int = #column
) {
self.keyPath = keyPath
self.fileID = fileID
self.function = function
self.line = line
self.column = column

self.observedObject = Application.dependency(
keyPath,
fileID,
function,
line,
column
)
}
}
#endif
53 changes: 53 additions & 0 deletions Tests/AppStateTests/ObservedDependencyTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#if !os(Linux) && !os(Windows)
import SwiftUI
import XCTest
@testable import AppState

fileprivate class ObservableService: ObservableObject {
@Published var count: Int

init() {
count = 0
}
}

fileprivate extension Application {
var test: Dependency<String> {
dependency("!!!")
}

var observableService: Dependency<ObservableService> {
dependency(ObservableService())
}
}

fileprivate struct ExampleDependencyWrapper {
@ObservedDependency(\.observableService) var service

func test() {
service.count += 1

_ = Picker("", selection: $service.count, content: EmptyView.init)
}
}

final class ObservedDependencyTests: XCTestCase {
override class func setUp() {
Application.logging(isEnabled: true)
}

override class func tearDown() {
Application.logger.debug("ObservedDependencyTests \(Application.description)")
}

func testDependency() {
let example = ExampleDependencyWrapper()

XCTAssertEqual(example.service.count, 0)

example.test()

XCTAssertEqual(example.service.count, 1)
}
}
#endif

0 comments on commit 18bd22f

Please sign in to comment.