Skip to content

Commit

Permalink
Add Documentation for the Main CardinalKit Module (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
PSchmiedmayer authored Feb 10, 2023
1 parent fc49949 commit 888d153
Show file tree
Hide file tree
Showing 38 changed files with 588 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import SwiftUI
/// Applications must ensure that an ``UsernamePasswordAccountService`` instance is injected in the SwiftUI environment by, e.g., using the `.environmentObject(_:)` view modifier.
///
/// The view can automatically validate input using passed in ``ValidationRule``s and can be customized using header or footer views:
/// ```
/// ```swift
/// UsernamePasswordLoginView(
/// passwordValidationRules: [
/// /* ... */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Views
/// Applications must ensure that an ``UsernamePasswordAccountService`` instance is injected in the SwiftUI environment by, e.g., using the `.environmentObject(_:)` view modifier.
///
/// The view can automatically validate input using passed in ``ValidationRule``s and can be customized using header or footer views:
/// ```
/// ```swift
/// UsernamePasswordResetPasswordView(
/// usernameValidationRules: [
/// /* ... */
Expand All @@ -35,7 +35,7 @@ import Views
/// }
/// )
/// .environmentObject(UsernamePasswordAccountService())
/// ``
/// ```
public struct UsernamePasswordResetPasswordView: View {
private let usernameValidationRules: [ValidationRule]
private let header: AnyView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// A rule used for validating text along with a message to display if the validation fails.
///
/// The following example demonstrates a ``ValidationRule`` using a regex expression for an email.
/// ```
/// ```swift
/// ValidationRule(
/// regex: try? Regex("[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"),
/// message: "The entered email is not correct."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Views
/// Applications must ensure that an ``UsernamePasswordAccountService`` instance is injected in the SwiftUI environment by, e.g., using the `.environmentObject(_:)` view modifier.
///
/// The view can automatically validate input using passed in ``ValidationRule``s and can be customized using header or footer views:
/// ```
/// ```swift
/// UsernamePasswordSignUpView(
/// signUpOptions: [.usernameAndPassword, .name, .genderIdentity, .dateOfBirth],
/// passwordValidationRules: [
Expand Down
50 changes: 49 additions & 1 deletion Sources/CardinalKit/Adapter/Adapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,55 @@
/// A ``Adapter`` can be used to transfrom an input `DataChange` (`InputElement` and `InputRemovalContext`)
/// to an output `DataChange` (`OutputElement` and `OutputRemovalContext`).
///
/// Use the ``AdapterBuilder`` to offer developers to option to pass in a `Adapter` instance to your components.
///
/// The following example demonstrates a simple ``Adapter`` that transforms an `Int` to a `String` (both have been extended to conform to `Identifiable`).
/// The mapping function uses the ``DataChange/map(element:removalContext:)`` function.
/// ```swift
/// actor IntToStringAdapter: Adapter {
/// typealias InputElement = Int
/// typealias InputRemovalContext = InputElement.ID
/// typealias OutputElement = String
/// typealias OutputRemovalContext = OutputElement.ID
///
///
/// func transform(
/// _ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
/// ) async -> any TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>> {
/// asyncSequence.map { element in
/// element.map(
/// element: {
/// String($0.id)
/// },
/// removalContext: {
/// String($0.id)
/// }
/// )
/// }
/// }
/// }
/// ```
///
/// The ``SingleValueAdapter`` provides a even more convenient way to implement adapters if the transformation can be done on a item-by-item basis.
///
/// Use the ``AdapterBuilder`` to offer developers to option to pass in a `Adapter` instance to your components:
/// ```swift
/// final class DataSourceExample<T: Identifiable>: Component {
/// typealias ComponentStandard = ExampleStandard
/// typealias DataSourceExampleAdapter = Adapter<T, T.ID, ExampleStandard.BaseType, ExampleStandard.RemovalContext>
///
///
/// @StandardActor var standard: ExampleStandard
/// let adapter: any DataSourceExampleAdapter
///
///
/// init(@AdapterBuilder<ExampleStandard.BaseType, ExampleStandard.RemovalContext> adapter: () -> (any DataSourceExampleAdapter)) {
/// self.adapter = adapter()
/// }
///
///
/// // ...
/// }
/// ```
public protocol Adapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>: Actor {
/// The input element of the ``Adapter``
associatedtype InputElement: Identifiable, Sendable where InputElement.ID: Sendable
Expand Down
20 changes: 20 additions & 0 deletions Sources/CardinalKit/Adapter/AdapterBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ private actor ThreeAdapterChain<


/// A function builder used to generate data source registry adapter chains.
///
/// Use the ``AdapterBuilder`` to offer developers to option to pass in a `Adapter` instance to your components:
/// ```swift
/// final class DataSourceExample<T: Identifiable>: Component {
/// typealias ComponentStandard = ExampleStandard
/// typealias DataSourceExampleAdapter = Adapter<T, T.ID, ExampleStandard.BaseType, ExampleStandard.RemovalContext>
///
///
/// @StandardActor var standard: ExampleStandard
/// let adapter: any DataSourceExampleAdapter
///
///
/// init(@AdapterBuilder<ExampleStandard.BaseType, ExampleStandard.RemovalContext> adapter: () -> (any DataSourceExampleAdapter)) {
/// self.adapter = adapter()
/// }
///
///
/// // ...
/// }
/// ```
@resultBuilder
public enum AdapterBuilder<OutputElement: Identifiable & Sendable, OutputRemovalContext: Identifiable & Sendable> where OutputElement.ID: Sendable, OutputElement.ID == OutputRemovalContext.ID {
/// Required by every result builder to build combined results from statement blocks.
Expand Down
17 changes: 16 additions & 1 deletion Sources/CardinalKit/Adapter/DataChange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,22 @@
//


/// A ``DataChange`` tracks the addition or removel of elements across components.
/// A ``DataChange`` tracks the addition or removal of elements across components.
///
/// ``DataChange`` instances are used in ``DataSourceRegistry``s to identify incoming data. ``Adapter``s use them as the basis for the transform functions.
///
/// The ``DataChange/map(element:removalContext:)`` function provides a convenient way to transform one ``DataChange`` instance in an other ``DataChange`` instance:
/// ```swift
/// let element: DataChange<Int, Int> = .addition(42)
/// element.map(
/// element: {
/// String($0.id)
/// },
/// removalContext: {
/// String($0.id)
/// }
/// )
/// ```
public enum DataChange<
Element: Identifiable & Sendable,
RemovalContext: Identifiable & Sendable
Expand Down
47 changes: 47 additions & 0 deletions Sources/CardinalKit/Adapter/SingleValueAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,53 @@
/// A ``SingleValueAdapter`` is a ``Adapter`` that can be used to more simply transform data using the
/// ``SingleValueAdapter/transform(element:)`` and ``SingleValueAdapter/transform(removalContext:)`` functions.
///
/// The following example demonstrates a simple ``Adapter`` that transforms an `Int` to a `String` (both have been extended to conform to `Identifiable`).
/// The mapping function uses the ``DataChange/map(element:removalContext:)`` function.
/// ```swift
/// actor IntToStringAdapter: Adapter {
/// typealias InputElement = Int
/// typealias InputRemovalContext = InputElement.ID
/// typealias OutputElement = String
/// typealias OutputRemovalContext = OutputElement.ID
///
///
/// func transform(
/// _ asyncSequence: some TypedAsyncSequence<DataChange<InputElement, InputRemovalContext>>
/// ) async -> any TypedAsyncSequence<DataChange<OutputElement, OutputRemovalContext>> {
/// asyncSequence.map { element in
/// element.map(
/// element: {
/// String($0.id)
/// },
/// removalContext: {
/// String($0.id)
/// }
/// )
/// }
/// }
/// }
/// ```
///
/// The `IntToStringAdapter` can be transformed to a ``SingleValueAdapter`` by breaking up the
/// functionality in the ``SingleValueAdapter/transform(element:)`` and ``SingleValueAdapter/transform(removalContext:)`` methods:
/// ```swift
/// actor IntToStringSingleValueAdapter: SingleValueAdapter {
/// typealias InputElement = Int
/// typealias InputRemovalContext = InputElement.ID
/// typealias OutputElement = String
/// typealias OutputRemovalContext = OutputElement.ID
///
///
/// func transform(element: Int) throws -> String {
/// String(element)
/// }
///
/// func transform(removalContext: InputElement.ID) throws -> OutputElement.ID {
/// String(removalContext)
/// }
/// }
/// ```
///
/// See ``Adapter`` for more detail about data source registry adapters.
public protocol SingleValueAdapter<InputElement, InputRemovalContext, OutputElement, OutputRemovalContext>: Adapter {
/// Map the element of the transformed async streams from additions.
Expand Down
18 changes: 15 additions & 3 deletions Sources/CardinalKit/CardinalKit.docc/CardinalKit.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,33 @@ Open-source framework for rapid development of modern, interoperable digital hea

## Overview

This is an example for a DocC documentation for the CardinalKit package.
> Note: Refer to the <doc:Setup> instructions on how to integrate CardinalKit into your application!
CardinalKit introduces a standards-based modular approach to building digital health applications. A standard builds the shared repository of data mapped to a common understanding that is used to exchange data between CardinalKit modules.

We differentiate between five different types of modules:
- **Standard**: Acts as a shared repository and common ground of communication between modules. We, e.g., provide the FHIR standard as a great standard to build your digital health applications.
- **Data Sources**: Provide input to a standard and utilize the standard's data source registration functionality and adapters to transform data into the standardized format. Examples include the HealthKit data source.
- **Data Source User Interfaces**: Data source user interfaces are data sources that also present user interface components. This, e.g., includes the questionnaire module in the CardinalKit Swift Package.
- **Data Storage Providers**: Data storage providers obtain elements from a standard and persistently store them. Examples include uploading the data to a cloud storage provider such as the Firebase module.
- **Research Application User Interface**: Research application user interfaces display additional context in the application and include the onboarding, consent, and contacts modules to display great digital health applications.

![System Architecture of the CardinalKit Framework](SystemArchitecture)

> Note: CardinalKit relies on an ecosystem of modules. Think about what modules you want to build and contribute to the open-source community! Refer to <doc:ModuleCapabilities> to learn more about building your modules.
## Topics

### CardinalKit Setup

- <doc:Setup>
- ``CardinalKit/CardinalKit``
- ``CardinalKitAppDelegate``
- ``Standard``

### Modules

You can learn more about module capabilites at ...

- <doc:ModuleCapabilities>
- ``Module``

### Data Sources
Expand Down
112 changes: 107 additions & 5 deletions Sources/CardinalKit/CardinalKit.docc/ModuleCapabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,116 @@ SPDX-License-Identifier: MIT
-->

<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
Building a module provides an easy one-stop solution to support different CardinalKit components and provide this functionality to the CardinalKit ecosystem.

## Overview
## Components

A ``Component`` defines a software subsystem that can be configured as part of the ``CardinalKitAppDelegate/configuration``.

The ``Component/ComponentStandard`` defines what Standard the component supports.
The ``Component/configure()-m7ic`` method is called on the initialization of CardinalKit.

### The Component Standard

A ``Component`` can support any generic standard or add additional constraints using an optional where clause:
```swift
class ExampleComponent<ComponentStandard: Standard>: Component where ComponentStandard: /* ... */ {
/*... */
}
```

``Component``s can also specify support for only one specific ``Standard`` using a `typealias` definition:
```swift
class ExampleFHIRComponent: Component {
typealias ComponentStandard = FHIR
}
```

> Note: You can learn more about a ``Standard`` in the ``Standard`` docmentation.
### Dependencies

A ``Component`` can define the dependencies using the @``Component/Dependency`` property wrapper:
```swift
class ExampleComponent<ComponentStandard: Standard>: Component {
@Dependency var exampleComponentDependency = ExampleComponentDependency()
}
```

Some components do not need a default value assigned to the property if they provide a default configuration and conform to ``DefaultInitializable``.
```swift
class ExampleComponent<ComponentStandard: Standard>: Component {
@Dependency var exampleComponentDependency: ExampleComponentDependency
}
```

You can access the wrapped value of the ``Component/Dependency`` after the ``Component`` is configured using ``Component/configure()-5lup3``,
e.g. in the ``LifecycleHandler/willFinishLaunchingWithOptions(_:launchOptions:)-26h4k`` function.

> Note: You can learn more about a ``Component/Dependency`` in the ``Component/Dependency`` docmentation. You can also dynamically define dependencies using the ``Component/DynamicDependencies`` property wrapper.

## Component Capabilities

Components can also conform to different additional protocols to provide additional access to CardinalKit features.
- ``LifecycleHandler``: Delegate methods are related to the `UIApplication` and ``CardinalKit/CardinalKit`` lifecycle.
- ``ObservableObjectProvider``: A ``Component`` can conform to ``ObservableObjectProvider`` to inject `ObservableObject`s in the SwiftUI view hierarchy.

### LifecycleHandler

The ``LifecycleHandler`` delegate methods are related to the `UIApplication` and ``CardinalKit/CardinalKit`` lifecycle.

Conform to the `LifecycleHandler` protocol to get updates about the application lifecycle similar to the `UIApplicationDelegate` on an app basis.

You can, e.g., implement the following functions to get informed about the application launching and being terminated:
- ``LifecycleHandler/willFinishLaunchingWithOptions(_:launchOptions:)-26h4k``
- ``LifecycleHandler/applicationWillTerminate(_:)-8wh06``

### ObservableObjectProvider

A ``Component`` can conform to ``ObservableObjectProvider`` to inject `ObservableObject`s in the SwiftUI view hierarchy using ``ObservableObjectProvider/observableObjects-6w1nz``


Reference types conforming to `ObservableObject` can be used in SwiftUI views to inform a view about changes in the object.
You can create and use them in a view using `@ObservedObject` or get it from the SwiftUI environment using `@EnvironmentObject`.

A Component can conform to `ObservableObjectProvider` to inject `ObservableObject`s in the SwiftUI view hierarchy.
You define all `ObservableObject`s that should be injected using the ``ObservableObjectProvider/observableObjects-5nl18`` property.
```swift
class MyComponent<ComponentStandard: Standard>: ObservableObjectProvider {
public var observableObjects: [any ObservableObject] {
[/* ... */]
}
}
```

`ObservableObjectProvider` provides a default implementation of the ``ObservableObjectProvider/observableObjects-5nl18`` If your type conforms to `ObservableObject`
that just injects itself into the SwiftUI view hierarchy:
```swift
class MyComponent<ComponentStandard: Standard>: ObservableObject, ObservableObjectProvider {
@Published
var test: String

// ...
}
```


## Modules

All these component capabilities are combined in the ``Module`` protocol, making it an easy one-stop solution to support all these different functionalities and build a capable CardinalKit module.

A ``Module`` is a ``Component`` that also includes
- Conformance to a ``LifecycleHandler``
- Persistance in the ``CardinalKit`` instance's ``CardinalKit/CardinalKit/typedCollection`` (using a conformance to ``TypedCollectionKey``)
- Automatic injection in the SwiftUI view hierachy (``ObservableObjectProvider`` & `ObservableObject`)

<!--@START_MENU_TOKEN@-->Text<!--@END_MENU_TOKEN@-->

## Topics

### <!--@START_MENU_TOKEN@-->Group<!--@END_MENU_TOKEN@-->
### Modules & Component Capabilities

- <!--@START_MENU_TOKEN@-->``Symbol``<!--@END_MENU_TOKEN@-->
- ``Component``
- ``Module``
- ``LifecycleHandler``
- ``ObservableObjectProvider``
Loading

0 comments on commit 888d153

Please sign in to comment.