{"id":13465299,"url":"https://github.com/ra1028/swiftui-atom-properties","last_synced_at":"2025-04-12T09:30:20.033Z","repository":{"id":39750721,"uuid":"479854137","full_name":"ra1028/swiftui-atom-properties","owner":"ra1028","description":"⚛️ Atomic approach state management and dependency injection for SwiftUI","archived":false,"fork":false,"pushed_at":"2024-10-11T05:26:40.000Z","size":6753,"stargazers_count":294,"open_issues_count":2,"forks_count":16,"subscribers_count":8,"default_branch":"main","last_synced_at":"2024-10-29T23:30:44.732Z","etag":null,"topics":["architecture","async","atomic","combine","concurrency","data-binding","declarative","dependency-injection","reactive","service-locator","state-management","swift","swiftui","testability"],"latest_commit_sha":null,"homepage":"https://ra1028.github.io/swiftui-atom-properties/documentation/atoms","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ra1028.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"ra1028"}},"created_at":"2022-04-09T22:07:01.000Z","updated_at":"2024-10-28T07:08:12.000Z","dependencies_parsed_at":"2022-07-20T13:47:58.030Z","dependency_job_id":"bf4bdd99-8104-41af-b5ff-952a5433d2f7","html_url":"https://github.com/ra1028/swiftui-atom-properties","commit_stats":{"total_commits":161,"total_committers":7,"mean_commits":23.0,"dds":0.06211180124223603,"last_synced_commit":"7b1510dbb4ba672ae840b32eb00bad8a3c44dc40"},"previous_names":["ra1028/swiftui-atomic-architecture"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ra1028%2Fswiftui-atom-properties","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ra1028%2Fswiftui-atom-properties/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ra1028%2Fswiftui-atom-properties/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ra1028%2Fswiftui-atom-properties/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ra1028","download_url":"https://codeload.github.com/ra1028/swiftui-atom-properties/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248545890,"owners_count":21122240,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","async","atomic","combine","concurrency","data-binding","declarative","dependency-injection","reactive","service-locator","state-management","swift","swiftui","testability"],"created_at":"2024-07-31T15:00:26.238Z","updated_at":"2025-04-12T09:30:19.990Z","avatar_url":"https://github.com/ra1028.png","language":"Swift","funding_links":["https://github.com/sponsors/ra1028"],"categories":["Patterns","Architecture and State"],"sub_categories":["Vim"],"readme":"\u003ch1 align=\"center\"\u003eAtoms\u003c/h1\u003e\n\u003cp align=\"center\"\u003eAtomic approach state management and dependency injection for SwiftUI\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003ca href=\"https://ra1028.github.io/swiftui-atom-properties/documentation/atoms\"\u003e📔 API Reference\u003c/a\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/ra1028/swiftui-atom-properties/actions\"\u003e\u003cimg alt=\"build\" src=\"https://github.com/ra1028/swiftui-atom-properties/workflows/test/badge.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/ra1028/swiftui-atom-properties/releases/latest\"\u003e\u003cimg alt=\"release\" src=\"https://img.shields.io/github/v/release/ra1028/swiftui-atom-properties.svg\"/\u003e\u003c/a\u003e\n  \u003ca href=\"Package.swift\"\u003e\u003cimg alt=\"swift\" src=\"https://img.shields.io/badge/language-Swift-orange.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"Package.swift\"\u003e\u003cimg alt=\"platform\" src=\"https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20-green.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"license\" src=\"https://img.shields.io/badge/license-MIT-black.svg\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n- [Introduction](#introduction)\n- [Examples](#examples)\n- [Getting Started](#getting-started)\n  - [Documentation](#documentation)\n  - [Requirements](#requirements)\n  - [Installation](#installation)\n- [Basic Tutorial](#basic-tutorial)\n- [Guides](#guides)\n  - [AtomRoot](#atomroot)\n  - [Atom](#atom)\n  - [Modifier](#modifier)\n  - [Attribute](#attribute)\n  - [Property Wrapper](#property-wrapper)\n  - [Context](#context)\n  - [View](#view)\n  - [Techniques](#techniques)\n  - [Advanced Usage](#advanced-usage)\n  - [Dealing with Known SwiftUI Bugs](#dealing-with-known-swiftui-bugs)\n- [Contributing](#contributing)\n- [Acknowledgements](#acknowledgements)\n- [License](#license)\n\n---\n\n## Introduction\n\n\u003cp align=\"center\"\u003e\n\n|Reactive Data Binding|Effective Caching|Compile Safe\u003c/br\u003eDependency Injection|\n|:------------------------|:----------------|:--------------------------------|\n|Pieces of app data that can be accessed from anywhere propagate changes reactively.|Cache data during in use and recompute only when truly needed.|Successful compilation guarantees that dependency injection is ready.|\n\n\u003c/p\u003e\n\nAtoms offer a simple but practical capability to tackle the complexity of modern apps. It effectively integrates the solution for both state management and dependency injection while allowing us to rapidly build a robust and testable application.  \nBuilding state by compositing atoms automatically optimizes rendering based on its dependency graph. This solves the problem of performance degradation caused by extra re-render which occurs before you realize.  \n\n\u003cimg src=\"assets/diagram.png\" width=700\u003e\n\n### Quick Overview\n\n- Declare your primitive atoms.\n\n```swift\nstruct CounterAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Int {\n        0\n    }\n}\n```\n\n- Bind the atom to your views.\n\n```swift\nstruct CountStepper: View {\n    @WatchState(CounterAtom())\n    var count\n\n    var body: some View {\n        Stepper(value: $count) {}\n    }\n}\n```\n\n- Share state across views without passing a Binding.\n\n```swift\nstruct CounterView: View {\n    @Watch(CounterAtom())\n    var count\n\n    var body: some View {\n        VStack {\n            Text(\"Count: \\(count)\")\n            CountStepper()\n        }\n    }\n}\n```\n\n---\n\n## Examples\n\n| ![Counter](assets/example_counter.png) | ![Todo](assets/example_todo.png) | ![TMDB](assets/example_tmdb.png) | ![Map](assets/example_map.png) | ![Voice Memo](assets/example_voice_memo.png) | ![Time Travel](assets/example_time_travel.png) |\n|-|-|-|-|-|-|\n\n- [Counter](Examples/Packages/CrossPlatform/Sources/ExampleCounter)  \n\u003csub\u003eDemonstrates the minimum app using this library.\u003c/sub\u003e\n- [Todo](Examples/Packages/CrossPlatform/Sources/ExampleTodo)  \n\u003csub\u003eA simple todo app that has user interactions, showing how multiple atoms interact with each other.\u003c/sub\u003e\n- [The Movie DB](Examples/Packages/iOS/Sources/ExampleMovieDB)  \n\u003csub\u003eDemonstrates practical usage which close to a real-world app, using [TMDB](https://www.themoviedb.org/) API for asynchronous networking.\u003c/sub\u003e\n- [Map](Examples/Packages/iOS/Sources/ExampleMap)  \n\u003csub\u003eA simple but effective app that demonstrates how to wrap a framework in this library.\u003c/sub\u003e\n- [Voice Memo](Examples/Packages/iOS/Sources/ExampleVoiceMemo)  \n\u003csub\u003eDemonstrates how to decompose and manage complex states and dependencies into compact atoms. Created to mimic the [TCA's example](https://github.com/pointfreeco/swift-composable-architecture/tree/main/Examples/VoiceMemos).\u003c/sub\u003e\n- [Time Travel](Examples/Packages/iOS/Sources/ExampleTimeTravel)  \n\u003csub\u003eA simple demo that demonstrates how to do [time travel debugging](https://en.wikipedia.org/wiki/Time_travel_debugging) with this library.\u003c/sub\u003e\n\nEach example has a test target too to demonstrate how to test your atoms with dependency injection.  \nOpen `Examples/Project.xcodeproj` and play around with it!\n\n---\n\n## Getting Started\n\n### Documentation\n\n- [API Reference](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms)\n- [Example apps](Examples)\n\n### Requirements\n\n|       |Minimum Version|\n|------:|--------------:|\n|Swift  |5.10, 6.0      |\n|Xcode  |15.4, 16.1     |\n|iOS    |14.0           |\n|macOS  |11.0           |\n|tvOS   |14.0           |\n|watchOS|7.0            |\n\n### Installation\n\nThe module name of the package is `Atoms`. Choose one of the instructions below to install and add the following import statement to your source code.\n\n```swift\nimport Atoms\n```\n\n#### [Xcode Package Dependency](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app)\n\nFrom Xcode menu: `File` \u003e `Swift Packages...`\n\n```text\nhttps://github.com/ra1028/swiftui-atom-properties\n```\n\n#### [Swift Package Manager](https://www.swift.org/package-manager)\n\nIn your `Package.swift` file, first add the following to the package `dependencies`:\n\n```swift\n.package(url: \"https://github.com/ra1028/swiftui-atom-properties\"),\n```\n\nAnd then, include \"Atoms\" as a dependency for your target:\n\n```swift\n.target(name: \"\u003ctarget\u003e\", dependencies: [\n    .product(name: \"Atoms\", package: \"swiftui-atom-properties\"),\n]),\n```\n\n---\n\n## Basic Tutorial\n\nIn this tutorial, we are going to create a simple todo app as an example. This app will support to create/edit/filter todo items.  \n\nEvery view that uses atom must have an `AtomRoot` somewhere in the ancestor. In SwiftUI lifecycle apps, it's recommended to put it right under `WindowGroup`.\n\n```swift\n@main\nstruct TodoApp: App {\n    var body: some Scene {\n        WindowGroup {\n            AtomRoot {\n                TodoList()\n            }\n        }\n    }\n}\n```\n\nFirst, define a todo entity and an enum that represents filtering methods, and declare an atom with `StateAtom` that represents a mutable state.\n\n```swift\nstruct Todo {\n    var id: UUID\n    var text: String\n    var isCompleted: Bool\n}\n\nenum Filter: CaseIterable, Hashable {\n    case all, completed, uncompleted\n}\n\nstruct TodosAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e [Todo] {\n        []\n    }\n}\n\nstruct FilterAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Filter {\n        .all\n    }\n}\n```\n\nThe `FilteredTodosAtom` below represents the derived data that combines the above two atoms. You can think of derived data as the output of passing values to a pure function that derives a new value from the depending values.  \n\nWhen dependent data changes, the derived data reactively updates, and the output value is cached until it truly needs to be updated, so you don't need to worry about low performance due to the filter function being called each time the view recomputes.\n\n```swift\nstruct FilteredTodosAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e [Todo] {\n        let filter = context.watch(FilterAtom())\n        let todos = context.watch(TodosAtom())\n\n        switch filter {\n        case .all:         return todos\n        case .completed:   return todos.filter(\\.isCompleted)\n        case .uncompleted: return todos.filter { !$0.isCompleted }\n        }\n    }\n}\n```\n\nTo create a new todo item, you need to access to a writable value that update the value of `TodosAtom` you defined previously.  \n\n```swift\nstruct TodoCreator: View {\n    @WatchState(TodosAtom())\n    var todos\n\n    @State\n    var text = \"\"\n\n    var body: some View {\n        HStack {\n            TextField(\"Enter your todo\", text: $text)\n            Button(\"Add\") {\n                todos.append(Todo(id: UUID(), text: text, isCompleted: false))\n                text = \"\"\n            }\n        }\n    }\n}\n```\n\nSimilarly, build a view to switch the value of `FilterAtom`. Get a `Binding` to the value exposed by `@WatchState` using `$` prefix.\n\n```swift\nstruct TodoFilters: View {\n    @WatchState(FilterAtom())\n    var current\n\n    var body: some View {\n        Picker(\"Filter\", selection: $current) {\n            ForEach(Filter.allCases, id: \\.self) { filter in\n                switch filter {\n                case .all:         Text(\"All\")\n                case .completed:   Text(\"Completed\")\n                case .uncompleted: Text(\"Uncompleted\")\n                }\n            }\n        }\n        .pickerStyle(.segmented)\n    }\n}\n```\n\nNext, create a view to display a todo item. It also supports editing the item.\n\n```swift\nstruct TodoItem: View {\n    @WatchState(TodosAtom())\n    var allTodos\n\n    @State\n    var text: String\n\n    @State\n    var isCompleted: Bool\n\n    let todo: Todo\n\n    init(todo: Todo) {\n        self.todo = todo\n        self._text = State(initialValue: todo.text)\n        self._isCompleted = State(initialValue: todo.isCompleted)\n    }\n\n    var index: Int {\n        allTodos.firstIndex { $0.id == todo.id }!\n    }\n\n    var body: some View {\n        Toggle(isOn: $isCompleted) {\n            TextField(\"Todo\", text: $text) {\n                allTodos[index].text = text\n            }\n        }\n        .onChange(of: isCompleted) { isCompleted in\n            allTodos[index].isCompleted = isCompleted\n        }\n    }\n}\n```\n\nFinally, assemble the views you've created so far and complete.\n\n```swift\nstruct TodoList: View {\n    @Watch(FilteredTodosAtom())\n    var filteredTodos\n\n    var body: some View {\n        List {\n            TodoCreator()\n            TodoFilters()\n\n            ForEach(filteredTodos, id: \\.id) { todo in\n                TodoItem(todo: todo)\n            }\n        }\n    }\n}\n```\n\nThat is the basics for building apps using Atoms, but even asynchronous processes and more complex state management can be settled according to the same steps.  \nSee [Guides](#guides) section for more detail. Also, the [Examples](Examples) directory has several projects to explore concrete usage.\n\n---\n\n## Guides\n\nThis section introduces the available APIs and their uses.  \nTo look into the APIs in more detail, visit the [API referrence](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms).\n\n---\n\n### [AtomRoot](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomroot)\n\nThis view allows descendant views to use atoms. It must be the root of any views throughout the application.\n\n```swift\n@main\nstruct ExampleApp: App {\n    var body: some Scene {\n        WindowGroup {\n            AtomRoot {\n                ExampleView()\n            }\n        }\n    }\n}\n```\n\n---\n\n### Atom\n\nAn atom represents a piece of state and is the source of truth for your app. It can also represent a derived data by combining and transforming one or more other atoms.  \nEach atom does not actually have a global data inside, and retrieve values from the store provided by the `AtomRoot`. That's why *they can be accessed from anywhere, but never lose testability.*  \n\nAn atom and its value are associated using a unique `key` which is automatically defined if the atom conforms to `Hashable`, but you can also define it explicitly without Hashable.  \n\n ```swift\nstruct UserNameAtom: StateAtom {\n    let userID: Int\n\n    var key: Int {\n        userID\n    }\n\n    func defaultValue(context: Context) -\u003e String {\n        \"Robert\"\n    }\n}\n```\n\nIn order to provide the best interface and effective data-binding for the type of the resulting values, there are several variants of atoms as following.  \n\n#### [ValueAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/valueatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Provides a read-only value.|\n|Output     |`T`|\n|Use Case   |Computed property, Derived data, Dependency injection|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct LocaleAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e Locale {\n        .current\n    }\n}\n\nstruct LocaleView: View {\n    @Watch(LocaleAtom())\n    var locale\n\n    var body: some View {\n        Text(locale.identifier)\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [StateAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/stateatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Provides a read-write data.|\n|Output     |`T`|\n|Use Case   |Mutable data, Derived data|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct CounterAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Int {\n        0\n    }\n}\n\nstruct CounterView: View {\n    @WatchState(CounterAtom())\n    var count\n\n    var body: some View {\n        Stepper(\"Count: \\(count)\", value: $count)\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [TaskAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/taskatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Initiates a non-throwing `Task` from the given `async` function.|\n|Output     |`Task\u003cT, Never\u003e`|\n|Use Case   |Non-throwing asynchronous operation e.g. Expensive calculation|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct FetchUserAtom: TaskAtom, Hashable {\n    func value(context: Context) async -\u003e User? {\n        await fetchUser()\n    }\n}\n\nstruct UserView: View {\n    @Watch(FetchUserAtom())\n    var userTask\n\n    var body: some View {\n        Suspense(userTask) { user in\n            Text(user?.name ?? \"Unknown\")\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [ThrowingTaskAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/throwingtaskatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Initiates a throwing `Task` from the given `async throws` function.|\n|Output     |`Task\u003cT, any Error\u003e`|\n|Use Case   |Throwing asynchronous operation e.g. API call|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct FetchMoviesAtom: ThrowingTaskAtom, Hashable {\n    func value(context: Context) async throws -\u003e [Movie] {\n        try await fetchMovies()\n    }\n}\n\nstruct MoviesView: View {\n    @Watch(FetchMoviesAtom())\n    var moviesTask\n\n    var body: some View {\n        List {\n            Suspense(moviesTask) { movies in\n                ForEach(movies, id: \\.id) { movie in\n                    Text(movie.title)\n                }\n            } catch: { error in\n                Text(error.localizedDescription)\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [AsyncPhaseAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/asyncphaseatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Provides an `AsyncPhase` value that represents a result of the given asynchronous throwable function.|\n|Output     |`AsyncPhase\u003cT, E: Error\u003e` (`AsyncPhase\u003cT, any Error\u003e` in Swift 5)|\n|Use Case   |Throwing or non-throwing asynchronous operation e.g. API call|\n\nNote:  \nThe [typed throws](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md) feature introduced in Swift 6 allows the `Failure` type of the produced `AsyncPhase` to be specified as any type or even non-throwing, but in Swift 5 without it, the `Failure` type is always be `any Error`.  \nHere is a chart of the syntax in `typed throws` and the type of resulting `AsyncPhase`.  \n\n|Syntax             |Shorthand          |Produced                  |\n|:------------------|:------------------|:-------------------------|\n|`throws(E)`        |`throws(E)`        |`AsyncPhase\u003cT, E\u003e`        |\n|`throws(any Error)`|`throws`           |`AsyncPhase\u003cT, any Error\u003e`|\n|`throws(Never)`    |                   |`AsyncPhase\u003cT, Never\u003e`    |\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct FetchTrendingSongsAtom: AsyncPhaseAtom, Hashable {\n    func value(context: Context) async throws(FetchSongsError) -\u003e [Song] {\n        try await fetchTrendingSongs()\n    }\n}\n\nstruct TrendingSongsView: View {\n    @Watch(FetchTrendingSongsAtom())\n    var phase\n\n    var body: some View {\n        List {\n            switch phase {\n            case .success(let songs):\n                ForEach(songs, id: \\.id) { song in\n                    Text(song.title)\n                }\n\n            case .failure(.noData):\n                Text(\"There are no currently trending songs.\")\n\n            case .failure(let error):\n                Text(error.localizedDescription)\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [AsyncSequenceAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/asyncsequenceatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Provides an `AsyncPhase` value that represents asynchronous, sequential elements of the given `AsyncSequence`.|\n|Output     |`AsyncPhase\u003cT, any Error\u003e`|\n|Use Case   |Handle multiple asynchronous values e.g. web-sockets|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct NotificationAtom: AsyncSequenceAtom, Hashable {\n    let name: Notification.Name\n\n    func sequence(context: Context) -\u003e NotificationCenter.Notifications {\n        NotificationCenter.default.notifications(named: name)\n    }\n}\n\nstruct NotificationView: View {\n    @Watch(NotificationAtom(name: UIApplication.didBecomeActiveNotification))\n    var notificationPhase\n\n    var body: some View {\n        switch notificationPhase {\n        case .suspending, .failure:\n            Text(\"Unknown\")\n\n        case .success:\n            Text(\"Active\")\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [PublisherAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/publisheratom)\n\n|             |Description|\n|:------------|:----------|\n|Summary      |Provides an `AsyncPhase` value that represents sequence of values of the given `Publisher`.|\n|Output       |`AsyncPhase\u003cT, E: Error\u003e`|\n|Use Case     |Handle single or multiple asynchronous value(s) e.g. API call|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct TimerAtom: PublisherAtom, Hashable {\n    func publisher(context: Context) -\u003e AnyPublisher\u003cDate, Never\u003e {\n        Timer.publish(every: 1, on: .main, in: .default)\n            .autoconnect()\n            .eraseToAnyPublisher()\n    }\n}\n\nstruct TimerView: View {\n    @Watch(TimerAtom())\n    var timerPhase\n\n    var body: some View {\n        if let date = timerPhase.value {\n            Text(date.formatted(date: .numeric, time: .shortened))\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [ObservableObjectAtom](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/observableobjectatom)\n\n|           |Description|\n|:----------|:----------|\n|Summary    |Instantiates an observable object.|\n|Output     |`T: ObservableObject`|\n|Use Case   |Mutable complex state object|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nclass Contact: ObservableObject {\n    @Published var name = \"\"\n    @Published var age = 20\n\n    func haveBirthday() {\n        age += 1\n    }\n}\n\nstruct ContactAtom: ObservableObjectAtom, Hashable {\n    func object(context: Context) -\u003e Contact {\n        Contact()\n    }\n}\n\nstruct ContactView: View {\n    @WatchStateObject(ContactAtom())\n    var contact\n\n    var body: some View {\n        VStack {\n            TextField(\"Enter your name\", text: $contact.name)\n            Text(\"Age: \\(contact.age)\")\n            Button(\"Celebrate your birthday!\") {\n                contact.haveBirthday()\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n### Modifier\n\nModifiers can be applied to an atom to produce a different versions of the original atom to make it more coding friendly or to reduce view re-computation for performance optimization.\n\n#### [changes(of:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atom/changes(of:))\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |Derives a partial property with the specified key path from the original atom and prevent it from updating its downstream when its new value is equivalent to old value.|\n|Output         |`T: Equatable`|\n|Compatible     |All atoms types. The derived property must be `Equatable` compliant.|\n|Use Case       |Performance optimization, Property scope restriction|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct CountAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Int {\n        12345\n    }\n}\n\nstruct CountDisplayView: View {\n    @Watch(CountAtom().changes(of: \\.description))\n    var description  // : String\n\n    var body: some View {\n        Text(description)\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [changes](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atom/changes)\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |Prevents the atom from updating its child views or atoms when its new value is the same as its old value.|\n|Output         |`T: Equatable`|\n|Compatible     |All atom types that produce `Equatable` compliant value.|\n|Use Case       |Performance optimization|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct CountAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Int {\n        12345\n    }\n}\n\nstruct CountDisplayView: View {\n    @Watch(CountAtom().changes)\n    var count  // : Int\n\n    var body: some View {\n        Text(count.description)\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [animation(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atom/animation(_:))\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |Animates the view watching the atom when the value updates.|\n|Output         |`T`|\n|Compatible     |All atom types.|\n|Use Case       |Apply animation to a view|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct TextAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e String {\n        \"\"\n    }\n}\n\nstruct ExampleView: View {\n    @Watch(TextAtom().animation())\n    var text\n\n    var body: some View {\n        Text(text)\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [TaskAtom/phase](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/taskatom/phase) | [ThrowingTaskAtom/phase](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/throwingtaskatom/phase)\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |Converts the `Task` that the original atom provides into `AsyncPhase`.|\n|Output         |`AsyncPhase\u003cT, E: Error\u003e`|\n|Compatible     |`TaskAtom`, `ThrowingTaskAtom`|\n|Use Case       |Consume asynchronous result as `AsyncPhase`|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct FetchWeatherAtom: ThrowingTaskAtom, Hashable {\n    func value(context: Context) async throws -\u003e Weather {\n        try await fetchWeather()\n    }\n}\n\nstruct WeatherReportView: View {\n    @Watch(FetchWeatherAtom().phase)\n    var weatherPhase  // : AsyncPhase\u003cWeather, any Error\u003e\n\n    var body: some View {\n        switch weatherPhase {\n        case .suspending:\n            Text(\"Loading.\")\n\n        case .success(let weather):\n            Text(\"It's \\(weather.description) now!\")\n\n        case .failure:\n            Text(\"Failed to get weather data.\")\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n### Attribute\n\nThe attributes allow control over how the atoms essentially work, for example, cache control of the state.\n\n#### [Scoped](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/scoped)\n\n`Scoped` preserves the atom state in the scope nearest to the ancestor of where it is used and prevents it from being shared out of scope.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\nIn the example case below, each `SearchPane` uses the `SearchQueryAtom` state isolated for each scope.\n\n```swift\nstruct SearchQueryAtom: StateAtom, Scoped, Hashable {\n    func defaultValue(context: Context) -\u003e String {\n         \"\"\n    }\n}\n\nVStack {\n    AtomScope {\n        SearchPane()\n    }\n\n    AtomScope {\n        SearchPane()\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [KeepAlive](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/keepalive)\n\n`KeepAlive` allows the atom to preserve its data even if it's no longer watched from anywhere.  \n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\nIn the example case below, once master data is obtained from the server, it can be cached in memory until the app process terminates.\n\n```swift\nstruct FetchMasterDataAtom: ThrowingTaskAtom, KeepAlive, Hashable {\n    func value(context: Context) async throws -\u003e MasterData {\n        try await fetchMasterData()\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [Refreshable](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/refreshable)\n\n`Refreshable` allows you to implement a custom refreshable behavior to an atom.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\nIt adds custom refresh behavior to `ValueAtom` which is inherently unable to refresh.  \nIt's useful when need to have arbitrary refresh behavior or implementing refresh when value depends on private atom.  \nIn this example, `FetchMoviesPhaseAtom` transparently exposes the value of `FetchMoviesTaskAtom` as `AsyncPhase` so that the error can be handled easily inside the atom, and `Refreshable` gives refreshing behavior to `FetchMoviesPhaseAtom` itself.  \n\n```swift\nprivate struct FetchMoviesTaskAtom: ThrowingTaskAtom, Hashable {\n    func value(context: Context) async throws -\u003e [Movies] {\n        try await fetchMovies()\n    }\n}\n\nstruct FetchMoviesPhaseAtom: ValueAtom, Refreshable, Hashable {\n    func value(context: Context) -\u003e AsyncPhase\u003c[Movies], any Error\u003e {\n        context.watch(FetchMoviesTaskAtom().phase)\n    }\n\n    func refresh(context: CurrentContext) async -\u003e AsyncPhase\u003c[Movies], any Error\u003e {\n        await context.refresh(FetchMoviesTaskAtom().phase)\n    }\n\n    func effect(context: CurrentContext) -\u003e some AtomEffect {\n        UpdateEffect {\n            if case .failure = context.read(self) {\n                print(\"Failed to fetch movies.\")\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [Resettable](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/resettable)\n\n`Resettable` allows you to implement a custom reset behavior to an atom.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\nIt adds custom reset behavior to an atom that will be executed upon atom reset.  \nIt's useful when need to have arbitrary reset behavior or implementing reset when value depends on private atom.  \nIn following example, `RandomIntAtom` generates a random value using generated from private `RandomNumberGeneratorAtom`, and `Resettable` gives ability to replace exposed reset with  `RandomNumberGeneratorAtom` reset.  \n\n```swift\nstruct RandomIntAtom: ValueAtom, Resettable, Hashable {\n    func value(context: Context) -\u003e Int {\n        var generator = context.watch(RandomNumberGeneratorAtom())\n        return .random(in: 0..\u003c100, using: \u0026generator)\n    }\n\n    func reset(context: CurrentContext) {\n        context.reset(RandomNumberGeneratorAtom())\n    }\n}\n\nprivate struct RandomNumberGeneratorAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e CustomRandomNumberGenerator {\n        CustomRandomNumberGenerator()\n    }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n### Property Wrapper\n\nThe following property wrappers are used to bind atoms to view and recompute the view with data changes.  \nBy retrieving the atom through these property wrappers, the internal system marks the atom as in-use and the values are cached until that view is dismantled.\n\n#### [@Watch](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/watch)\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |This property wrapper is similar to `@State` or `@Environment`, but is always read-only. It recomputes the view with value changes.|\n|Compatible     |All atom types|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct UserNameAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e String {\n        \"John\"\n    }\n}\n\nstruct UserNameDisplayView: View {\n    @Watch(UserNameAtom())\n    var name\n\n    var body: some View {\n        Text(\"User name: \\(name)\")\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [@WatchState](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/watchstate)\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |This property wrapper is read-write as the same interface as `@State`. It recomputes the view with data changes. You can get a `Binding` to the value using `$` prefix.|\n|Compatible     |`StateAtom`|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct UserNameAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e String {\n        \"Jim\"\n    }\n}\n\nstruct UserNameInputView: View {\n    @WatchState(UserNameAtom())\n    var name\n\n    var body: some View {\n        VStack {\n            TextField(\"User name\", text: $name)\n            Button(\"Clear\") {\n                name = \"\"\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [@WatchStateObject](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/watchstateobject)\n\n|               |Description|\n|:--------------|:----------|\n|Summary        |This property wrapper has the same interface as `@StateObject` and `@ObservedObject`. It recomputes the view when the observable object updates. You can get a `Binding` to one of the observable object's properties using `$` prefix.|\n|Compatible     |`ObservableObjectAtom`|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nclass Counter: ObservableObject {\n    @Published var count = 0\n\n    func plus(_ value: Int) {\n        count += value\n    }\n}\n\nstruct CounterAtom: ObservableObjectAtom, Hashable {\n    func object(context: Context) -\u003e Counter {\n        Counter()\n    }\n}\n\nstruct CounterView: View {\n    @WatchStateObject(CounterObjectAtom())\n    var counter\n\n    var body: some View {\n        VStack {\n            Text(\"Count: \\(counter.count)\")\n            Stepper(value: $counter.count) {}\n            Button(\"+100\") {\n                counter.plus(100)\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [@ViewContext](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/viewcontext)\n\nUnlike the property wrappers described the above, this property wrapper is not intended to bind single atom. It provides an `AtomViewContext` to the view, allowing for more functional control of atoms.  \nFor instance, the following controls can only be done through the context.  \n\n- `refresh(_:)` operator to reset an asynchronous atom value and wait for its completion.\n\n```swift\nawait context.refresh(FetchMoviesAtom())\n```\n\n- `reset(_:)` operator to clear the current atom value.\n\n```swift\ncontext.reset(CounterAtom())\n```\n\nThe context also provides a flexible solution for passing dynamic parameters to atom's initializer. See [Context](#context) section for more detail.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct FetchBookAtom: ThrowingTaskAtom, Hashable {\n    let id: Int\n\n    func value(context: Context) async throws -\u003e Book {\n        try await fetchBook(id: id)\n    }\n}\n\nstruct BookView: View {\n    @ViewContext\n    var context\n\n    let id: Int\n\n    var body: some View {\n        let task = context.watch(FetchBookAtom(id: id))\n\n        Suspense(task) { book in\n            Text(book.content)\n        } suspending: {\n            ProgressView()\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n### Context\n\nContext is a structure for using and interacting with atom values from views or other atoms.  \n\n|API|Use|\n|:--|:--|\n|[watch(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/watch(_:))|Gets an atom value and starts watching its update.|\n|[read(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomcontext/read(_:))|Gets an atom value but does not watch its update.|\n|[set(_:for:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomcontext/set(_:for:))|Sets a new value to the atom.|\n|[modify(_:body:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomcontext/modify(_:body:))|Modifies the cached atom value.|\n|[subscript[]](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomcontext/subscript(_:))|Read-write access for applying mutating methods.|\n|[refresh(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomcontext/refresh(_:)-7xzm9)|Produce a new value of the atom after waiting until asynchronous operation is complete.|\n|[reset(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomcontext/reset(_:)-8u78a)|Reset an atom to the default value or a first output.|\n\nContexts are provided in the following types depending on the environment where they are provided. In addition to the common APIs described above, each context type may have its unique functionalities.  \n\n#### [AtomViewContext](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomviewcontext)\n\nA context available through the `@ViewContext` property wrapper when using atoms from a view.\n\n|API|Use|\n|:--|:--|\n|[binding(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomviewcontext/binding(_:))|Gets a binding to the atom state.|\n|[snapshot()](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomviewcontext/snapshot())|For debugging, takes a snapshot that captures specific set of values of atoms.|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct SearchQueryAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e String {\n        \"\"\n    }\n}\n\nstruct FetchBooksAtom: ThrowingTaskAtom, Hashable {\n    func value(context: Context) async throws -\u003e [Book] {\n        let query = context.watch(SearchQueryAtom())\n        return try await fetchBooks(query: query)\n    }\n}\n\nstruct BooksView: View {\n    @ViewContext\n    var context: AtomViewContext\n\n    var body: some View {\n        // watch\n        let booksTask = context.watch(FetchBooksAtom())     // Task\u003c[Book], any Error\u003e\n        // binding\n        let searchQuery = context.binding(SearchQueryAtom())  // Binding\u003cString\u003e\n\n        List {\n            Suspense(booksTask) { books in\n                ForEach(books, id: \\.isbn) { book in\n                    Text(\"\\(book.title): \\(book.isbn)\")\n                }\n            }\n        }\n        .searchable(text: searchQuery)\n        .refreshable {\n            // refresh\n            await context.refresh(FetchBooksAtom())\n        }\n        .toolbar {\n            ToolbarItem(placement: .bottomBar) {\n                HStack {\n                    Button(\"Reset\") {\n                        // reset\n                        context.reset(SearchQueryAtom())\n                    }\n                    Button(\"All\") {\n                        // set\n                        context.set(\"All\", for: SearchQueryAtom())\n                    }\n                    Button(\"Space\") {\n                        // subscript\n                        context[SearchQueryAtom()].append(\" \")\n                    }\n                    Button(\"Print\") {\n                        // read\n                        let query = context.read(SearchQueryAtom())\n                        print(query)\n                    }\n                    Button(\"Snapshot\") {\n                        // snapshot\n                        let snapshot = context.snapshot()\n                        print(snapshot)\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [AtomTransactionContext](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtransactioncontext)\n\nA context passed as a parameter to the primary function of each atom type.  \n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nfinal class LocationManagerDelegate: NSObject, CLLocationManagerDelegate { ... }\n\nstruct LocationManagerDelegateAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e LocationManagerDelegate {\n        LocationManagerDelegate()\n    }\n}\n\nstruct LocationManagerAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e any LocationManagerProtocol {\n        let delegate = context.watch(LocationManagerDelegateAtom())\n        let manager = CLLocationManager()\n        manager.delegate = delegate\n        return manager\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### [AtomTestContext](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext)\n\nA context that can simulate any scenarios in which atoms are used from a view or another atom and provides a comprehensive means of testing.\n\n|API|Use|\n|:--|:--|\n|[lookup(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/lookup(_:))|Gets an atom value without creating a cache.|\n|[unwatch(_:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/unwatch(_:))|Simulates a scenario in which the atom is no longer watched.|\n|[override(_:with:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/override(_:with:)-82t4q)|Overwrites the output of a specific atom or all atoms of the given type with the fixed value.|\n|[waitForUpdate(timeout:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/waitforupdate(timeout:))|Waits until any of the atoms watched through this context have been updated.|\n|[wait(for:timeout:until:)](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/wait(for:timeout:until:))|Waits for the given atom until it will be a certain state.|\n|[onUpdate](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomtestcontext/onupdate)|Sets a closure that notifies there has been an update to one of the atoms.|\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nprotocol APIClientProtocol {\n    func fetchMusics() async throws -\u003e [Music]\n}\n\nstruct APIClient: APIClientProtocol { ... }\nstruct MockAPIClient: APIClientProtocol { ... }\n\nstruct APIClientAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e any APIClientProtocol {\n        APIClient()\n    }\n}\n\nstruct FetchMusicsAtom: ThrowingTaskAtom, Hashable {\n    func value(context: Context) async throws -\u003e [Music] {\n        let api = context.watch(APIClientAtom())\n        return try await api.fetchMusics()\n    }\n}\n\n@MainActor\nclass FetchMusicsTests: XCTestCase {\n    func testFetchMusicsAtom() async throws {\n        let context = AtomTestContext()\n\n        context.override(APIClientAtom()) { _ in\n            MockAPIClient()\n        }\n\n        let musics = try await context.watch(FetchMusicsAtom()).value\n\n        XCTAssertTrue(musics.isEmpty)\n    }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n### View\n\n#### [AtomScope](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomscope)\n\n`AtomScope` allows you to monitor changes or override atoms used in descendant views. Unlike `AtomRoot`, they affect only those in scope.  \nSee the [Atom Override](#atom-override) and [Debugging](#debugging) sections for specific uses.  \n\n```swift\nAtomScope {\n    CounterView()\n}\n.scopedObserve { snapshot in\n    if let count = snapshot.lookup(CounterAtom()) {\n        print(count)\n    }\n}\n```\n\n#### [AtomDerivedScope](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomderivedscope)\n\n`AtomDerivedScope` is useful when, for some reason, the atom store is not propagated through a view hierarchy. It explicitly propagate the atom store from the parent view via the given view context.  \n\n```swift\nstruct MailView: View {\n    @ViewContext\n    var context\n\n    var body: some View {\n        AtomDerivedScope(context) {\n            WrappedMailView()\n        }\n    }\n}\n```\n\n#### [Suspense](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/suspense)\n\n`Suspense` awaits the resulting value of the given `Task` and displays the content depending on its phase.  \nOptionally, you can pass `suspending` content to be displayed until the task completes, and pass `catch` content to be displayed if the task fails.  \n\n```swift\nstruct NewsView: View {\n    @Watch(LatestNewsAtom())\n    var newsTask: Task\u003cNews, any Error\u003e\n\n    var body: some View {\n        Suspense(newsTask) { news in\n            Text(news.content)\n        } suspending: {\n            ProgressView()\n        } catch: { error in\n            Text(error.localizedDescription)\n        }\n    }\n}\n```\n\n---\n\n### Techniques\n\n#### Scoped Atom\n\nThis library is designed with the shared state as a single source of truth first principle, but also the state can be scoped depending on the intended use.  \nScoped atoms preserves the atom state in the [AtomScope](#atomscope) nearest to the ancestor of where it is used and prevents it from being shared out of scope. `Scoped` is the attribute for that feature.  \n\n```swift\nstruct TextInputAtom: StateAtom, Scoped, Hashable {\n    func defaultValue(context: Context) -\u003e String {\n        \"\"\n    }\n}\n\nstruct TextInputView: View {\n    @Watch(TextInputAtom())\n    ...\n}\n\nVStack {\n    // The following two TextInputView don't share TextInputAtom state.\n\n    AtomScope {\n        TextInputView()\n    }\n\n    AtomScope {\n        TextInputView()\n    }\n}\n```\n\nWhen multiple `AtomScope`s are nested, and you want to store and share an atom state in the particular scope, it is able to define a scope ID which is to find a matching scope.  \n\n```swift\nstruct TextScopeID: Hashable {}\n\nstruct TextInputAtom: StateAtom, Scoped, Hashable {\n    var scopeID: TextScopeID {\n        TextScopeID()\n    }\n\n    func defaultValue(context: Context) -\u003e String {\n        \"\"\n    }\n}\n\nAtomScope(id: TextScopeID()) {\n    TextInputView()\n\n    AtomScope {\n        // Shares TextInputAtom state with the TextInputView placed in the parent scope.\n        TextInputView()\n    }\n}\n```\n\nThis is also useful when multiple identical screens are stacked and each screen needs isolated states such as user inputs.  \nNote that other atoms that depend on scoped atoms will be in a shared state and must be given `Scoped` attribute as well in order to scope them as well.  \n\n#### Atom Effect\n\nAtom effects are an API for managing side effects that are synchronized with the atom's lifecycle. They are widely applicable for variety of usage such as state synchronization, state persistence, logging, and etc, by observing and reacting to state changes.  \n\nYou can create custom effects that conform to the [`AtomEffect`](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atomeffect) protocol, but there are several predefined effects.  \n\n|API|Use|\n|:--|:--|\n|[InitializeEffect](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/initializeeffect)|Performs an arbitrary action when the atom is initialized.|\n|[UpdateEffect](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/updateeffect)|Performs an arbitrary action when the atom is updated.|\n|[ReleaseEffect](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/releaseeffect)|Performs an arbitrary action when the atom is released.|\n|[MergedEffect](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/mergedeffect)|Merges multiple atom effects into one.|\n\nAtom effects are attached to atoms via the [`Atom.effect(context:)`](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/atom/effect(context:)-4wm5m) function.  \n\n```swift\nstruct CounterAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Int {\n        UserDefaults.standard.integer(forKey: \"persistence_key\")\n    }\n\n    func effect(context: CurrentContext) -\u003e some AtomEffect {\n        UpdateEffect {\n            UserDefaults.standard.set(context.read(self), forKey: \"persistence_key\")\n        }\n    }\n}\n```\n\nEach atom initializes its effect when the atom is initialized, and the effect is retained until the atom is no longer used from anywhere and is released, thus it allows to declare stateful side effects.  \n\n```swift\nstruct CounterAtom: StateAtom, Hashable {\n    func defaultValue(context: Context) -\u003e Int {\n        0\n    }\n\n    func effect(context: CurrentContext) -\u003e some AtomEffect {\n        CountTimerEffect()\n    }\n}\n\n\nfinal class CountTimerEffect: AtomEffect {\n    private var timer: Timer?\n\n    func initialized(context: Context) {\n        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in\n            context[CounterAtom()] += 1\n        }\n    }\n\n    func updated(context: Context) {\n        print(\"Count: \\(context.read(CounterAtom()))\")\n    }\n\n    func released(context: Context) {\n        timer?.invalidate()\n        timer = nil\n    }\n}\n```\n\n#### Atom Override\n\nYou can override atoms in [AtomRoot](#atomroot) or [AtomScope](#atomscope) to overwirete the atom states for dependency injection or faking state in particular view, which is useful especially for testing.  \n\nOverriding in `AtomRoot` return the given value instead of the actual atom value no matter where the overridden atom is used in its descendant views.  \n\n```swift\n// Overrides the CounterAtom value to be `456` in anywhere in the ancestor.\nAtomRoot {\n    RootView()\n}\n.override(CounterAtom()) { _ in\n    456\n}\n```\n\nOn the other hand, overriding with `AtomScope` behaves similar to overriding in `AtomRoot`, but the atoms used in other scopes nested in the descendants are not overridden.  \n\n```swift\n// Overrides the CounterAtom value to be `456` only for this scope.\nAtomScope {\n    CountDisplay()\n\n    // CounterAtom is not overridden in this scope.\n    AtomScope {\n        CountDisplay()\n    }\n}\n.scopedOverride(CounterAtom()) { _ in\n    456\n}\n```\n\nNote that overridden atoms in `AtomScope` automatically be scoped, but other atoms that depend on them will be in a shared state and must be given `Scoped` attribute (See also: [Scoped Atom](#scoped-atom)) in order to avoid it from being shared across out of scope.  \n\nSee [Testing](#testing) section for details on dependency injection on unit tests.  \n\n#### Testing\n\nThis library naturally integrates dependency injection and data-binding to provide a comprehensive means of testing. It allows you to test per small atom such that you can keep writing simple test cases per smallest unit of state without compose all states into a huge object and supposing complex integration test scenarios.  \nIn order to fully test your app, this library guarantees the following principles:\n\n- Hermetic environment that no data is shared between test cases.\n- Dependencies are replaceable with any of mock/stub/fake/spy per test case.\n- Test cases can reproduce any possible scenarios at the view-layer.\n\nIn the test case, you first create an `AtomTestContext` instance that behaves similarly to other context types. The context allows for flexible reproduction of expected scenarios for testing using the control functions described in the [Context](#context) section.  \nIn addition, it's able to replace the atom value with test-friendly dependencies with `override` function. It helps you to write a reproducible \u0026 stable testing.  \nSince atom needs to be used from the main actor to guarantee thread-safety, functions that tests atoms should have `@MainActor` attribute.\n\n\u003cdetails\u003e\u003csummary\u003eClick to expand the classes to be tested\u003c/summary\u003e\n\n```swift\n\nstruct Book: Equatable {\n    var title: String\n    var isbn: String\n}\n\nprotocol APIClientProtocol {\n    func fetchBook(isbn: String) async throws -\u003e Book\n}\n\nstruct APIClient: APIClientProtocol {\n    func fetchBook(isbn: String) async throws -\u003e Book {\n        ... // Networking logic.\n    }\n}\n\nclass MockAPIClient: APIClientProtocol {\n    var response: Book?\n\n    func fetchBook(isbn: String) async throws -\u003e Book {\n        guard let response else {\n            throw URLError(.unknown)\n        }\n        return response\n    }\n}\n\nstruct APIClientAtom: ValueAtom, Hashable {\n    func value(context: Context) -\u003e any APIClientProtocol {\n        APIClient()\n    }\n}\n\nstruct FetchBookAtom: ThrowingTaskAtom, Hashable {\n    let isbn: String\n\n    func value(context: Context) async throws -\u003e Book {\n        let api = context.watch(APIClientAtom())\n        return try await api.fetchBook(isbn: isbn)\n    }\n}\n\n```\n\n\u003c/details\u003e\n\n```swift\n\nclass FetchBookTests: XCTestCase {\n    @MainActor\n    func testFetch() async throws {\n        let context = AtomTestContext()\n        let api = MockAPIClient()\n\n        // Override the atom value with the mock instance.\n        context.override(APIClientAtom()) { _ in\n            api\n        }\n\n        let expected = Book(title: \"A book\", isbn: \"ISBN000–0–0000–0000–0\")\n\n        // Inject the expected response to the mock.\n        api.response = expected\n\n        let book = try await context.watch(FetchBookAtom(isbn: \"ISBN000–0–0000–0000–0\")).value\n\n        XCTAssertEqual(book, expected)\n    }\n}\n```\n\n#### Debugging\n\nThis library defines a Directed Acyclic Graph (DAG) internally to centrally manage atom states, making it easy to analyze its dependencies and where they are (or are not) being used.  \nThere are the following two ways to get a [Snapshot](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/snapshot) of the dependency graph at a given point in time.  \n\nThe first is to get `Snapshot` through [@ViewContext](#atomviewcontext). This API is suitable for obtaining and analyzing debugging information on demand.  \n\n```swift\n@ViewContext\nvar context\n\nvar debugButton: some View {\n    Button(\"Dump dependency graph\") {\n        let snapshot = context.snapshot()\n        print(snapshot.graphDescription())\n    }\n}\n```\n\nOr, you can observe all state changes and always continue to receive `Snapshots` at that point in time with `observe(_:)` modifier of [AtomRoot](#atomroot) or with `scopedObserve(_:)` modifier of [AtomScope](#atomscope).  \nNote that observing in `AtomRoot` will receive every state changes that happened in the whole app, but observing in `AtomScope` will observe changes of atoms used in the scope.  \n\n```swift\nAtomRoot {\n    HomeScreen()\n}\n.observe { snapshot in\n    print(snapshot.graphDescription())\n}\n```\n\n`@ViewContext` also supports restoring the values of atoms and the dependency graph captured at a point in time in a retrieved snapshot and its dependency graph so that you can investigate what happend.  \nThe debugging technique is called [time travel debugging](https://en.wikipedia.org/wiki/Time_travel_debugging), and the example application [here](Examples/Packages/iOS/Sources/ExampleTimeTravel) demonstrates how it works.  \n\n```swift\n@ViewContext\nvar context\n\n@State\nvar snapshot: Snapshot?\n\nvar body: some View {\n    VStack {\n        Button(\"Capture\") {\n            snapshot = context.snapshot()\n        }\n        Button(\"Restore\") {\n            if let snapshot {\n                context.restore(snapshot)\n            }\n        }\n    }\n}\n```\n\nIn addition, [graphDescription()](https://ra1028.github.io/swiftui-atom-properties/documentation/atoms/snapshot/graphdescription()) method returns a string, that represents the dependencies graph and where they are used, as a String in [graph description language DOT](https://graphviz.org/doc/info/lang.html).  \nThis can be converted to an image using [Graphviz](https://graphviz.org), a graph visualization tool, to visually analyze information about the state of the application, as shown below.  \n\n\u003cimg src=\"assets/dependency_graph.png\" alt=\"Dependency Graph\" width=\"50%\" align=\"right\"\u003e\n\n```dot\ndigraph {\n  node [shape=box]\n  \"FilterAtom\"\n  \"FilterAtom\" -\u003e \"TodoApp/FilterPicker.swift\" [label=\"line:3\"]\n  \"FilterAtom\" -\u003e \"FilteredTodosAtom\"\n  \"TodosAtom\"\n  \"TodosAtom\" -\u003e \"FilteredTodosAtom\"\n  \"FilteredTodosAtom\"\n  \"FilteredTodosAtom\" -\u003e \"TodoApp/TodoList.swift\" [label=\"line:5\"]\n  \"TodoApp/TodoList.swift\" [style=filled]\n  \"TodoApp/FilterPicker.swift\" [style=filled]\n}\n```\n\n#### Preview\n\nEven in SwiftUI previews, the view must have an `AtomRoot` somewhere in the ancestor.  \nTo inject dependencies so that display a static preview, define the dependencies as atoms and override them.  \n\n```swift\nstruct NewsList_Preview: PreviewProvider {\n    static var previews: some View {\n        AtomRoot {\n            NewsList()\n        }\n        .override(APIClientAtom()) { _ in\n            StubAPIClient()\n        }\n    }\n}\n```\n\n See [Override Atoms](#override-atoms) section for more details of dependency injection.  \n\n---\n\n### Advanced Usage\n\n#### Use atoms without watching\n\nThe `read(_:)` function is a way to get the data of an atom without having watch to and receiving future updates of it. It's commonly used inside functions triggered by call-to-actions.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct TextAtom: StateAtom, Hashable {\n    func value(context: Context) -\u003e String {\n        \"\"\n    }\n}\n\nstruct TextCopyView: View {\n    @ViewContext\n    var context\n\n    var body: some View {\n        Button(\"Copy\") {\n            UIPasteboard.general.string = context.read(TextAtom())\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### Dynamically initiate atom families\n\nEach atom must have a unique `key` to be uniquely associated with its value. As described in the [Atom](#atom) section, it is automatically synthesized by conforming to `Hashable`, but with explicitly specifying a `key` allowing you to pass arbitrary external parameters to the atom. It is commonly used, for example, to retrieve user information associated with a dynamically specified ID from a server.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct FetchUserAtom: ThrowingTaskAtom {\n    let id: Int\n\n    // This atom can also conforms to `Hashable` in this case,\n    // but this example specifies the key explicitly.\n    var key: Int {\n        id\n    }\n\n    func value(context: Context) async throws -\u003e Value {\n        try await fetchUser(id: id)\n    }\n}\n\nstruct UserView: View {\n    let id: Int\n\n    @ViewContext\n    var context\n\n    var body: some View {\n        let task = context.watch(FetchUserAtom(id: id))\n\n        Suspense(task) { user in\n            VStack {\n                Text(\"Name: \\(user.name)\")\n                Text(\"Age: \\(user.age)\")\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### Use atoms inside objects\n\nYou can pass a context to your object and interact with other atoms at any asynchronous timing. However, in that case, when the `watch` is called, it end up with the object instance itself will be re-created with fresh data. Therefore, you can explicitly prevent the use of the `watch` by passing it as `AtomContext` type.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e📖 Example\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct MessageLoaderAtom: ObservableObjectAtom, Hashable {\n    func object(context: Context) -\u003e MessageLoader {\n        MessageLoader(context: context)\n    }\n}\n\n@MainActor\nclass MessageLoader: ObservableObject {\n    let context: AtomContext\n\n    @Published\n    var phase = AsyncPhase\u003c[Message], any Error\u003e.suspending\n\n    init(context: AtomContext) {\n        self.context = context\n    }\n\n    func load() async {\n        let api = context.read(APIClientAtom())\n        phase = await AsyncPhase {\n            try await api.fetchMessages(offset: 0)\n        }\n    }\n\n    func loadNext() async {\n        guard let messages = phase.value else {\n            return\n        }\n\n        let api = context.read(APIClientAtom())\n        let nextPhase = await AsyncPhase {\n            try await api.fetchMessages(offset: messages.count)\n        }\n        phase = nextPhase.map { messages + $0 }\n    }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n### Dealing with Known SwiftUI Bugs\n\n#### Modal presentation causes assertionFailure when dismissing it (Fixed in iOS15)\n\nUnfortunately, SwiftUI has a bug in iOS14 or lower where the `EnvironmentValue` is removed from a screen presented with `.sheet` just before dismissing it. Since this library is designed based on `EnvironmentValue`, this bug end up triggering the friendly `assertionFailure` that is added so that developers can easily aware of forgotten `AtomRoot` implementation.  \nAs a workaround, you can use `AtomDerivedScope` to explicitly propagate the atom store via `AtomViewContext` from the parent view.\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e💡 Click to expand workaround\u003c/code\u003e\u003c/summary\u003e\n\n```swift\nstruct RootView: View {\n    @State\n    var isPresented = false\n\n    @ViewContext\n    var context\n\n    var body: some View {\n        VStack {\n            Text(\"Example View\")\n        }\n        .sheet(isPresented: $isPresented) {\n            AtomDerivedScope(context) {\n                MailView()\n            }\n        }\n    }\n}\n```\n\n\u003c/details\u003e\n\n#### Some SwiftUI modifiers cause memory leak (Fixed in iOS16)\n\nIn iOS 15 or lower, some modifiers in SwiftUI seem to cause an internal memory leak if it captures `self` implicitly or explicitly. To avoid that bug, make sure that `self` is not captured when using those modifiers.  \nBelow are the list of modifiers I found that cause memory leaks:\n\n- [`refreshable(action:)`](https://developer.apple.com/documentation/SwiftUI/View/refreshable(action:))\n- [`onSubmit(of:_:)`](https://developer.apple.com/documentation/swiftui/view/onsubmit(of:_:))\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003e💡 Click to expand workaround\u003c/code\u003e\u003c/summary\u003e\n\n```swift\n@ViewContext\nvar context\n\n...\n\n.refreshable { [context] in\n    await context.refresh(FetchDataAtom())\n}\n```\n\n```swift\n@State\nvar isShowingSearchScreen = false\n\n...\n\n.onSubmit { [$isShowingSearchScreen] in\n    $isShowingSearchScreen.wrappedValue = true\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## Contributing\n\nAny type of contribution is welcome! e.g.\n\n- Give it star ⭐ \u0026 fork this repository.\n- Report bugs with reproducible steps.\n- Propose new features.\n- Add more documentations.\n- Provide repos of sample apps using this library.\n- Become a maintainer after making multiple contributions.\n- Become a [sponsor](https://github.com/sponsors/ra1028).\n\n---\n\n## Acknowledgements\n\n- [Recoil](https://recoiljs.org)\n- [Riverpod](https://riverpod.dev)\n- [Jotai](https://github.com/pmndrs/jotai)\n\n---\n\n## License\n\n[MIT © Ryo Aoyama](LICENSE)\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fra1028%2Fswiftui-atom-properties","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fra1028%2Fswiftui-atom-properties","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fra1028%2Fswiftui-atom-properties/lists"}