Skip to content

Commit

Permalink
Adding documentation for the new onStoreDidLoad methods
Browse files Browse the repository at this point in the history
  • Loading branch information
mergesort committed Dec 31, 2024
1 parent 9a2408b commit 2baca64
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 0 deletions.
53 changes: 53 additions & 0 deletions Sources/Boutique/Documentation.docc/Articles/Using Stores.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,59 @@ func getItems() async -> [Item] {

The synchronous initializer is a sensible default, but if your app's needs dictate displaying data only once you've loaded all of the necessary items the asynchronous initializers are there to help.

## Observing Store Loading State

You can manually observe the loading state of a ``Store`` as we do in `getItems()` above, but Boutique also provides a ``onStoreDidLoad`` function to observe the loading state of a ``Store`` in SwiftUI.

```swift
struct ContentView: View {
@Stored var items: [Item]
@State var itemsHaveLoaded = false

var body: some View {
VStack {
AlwaysVisibleBanner()

if self.itemsHaveLoaded {
if self.items.isEmpty {
EmptyStateView()
} else {
ItemsView(items: self.items)
}
} else {
LoadingStateView()
}
}
.onStoreDidLoad(
self.$items,
update: $itemsHaveLoaded,
onError: { error in
log.error("Failed to load items", error)
}
)
}
}
```

This allows for a clean separation of Views to display across three different states:
- When the Store has finished loading and has items
- When the Store has finished loading and has no items
- When the Store is loading (and implicitly has no items)

You can also choose to use the closure-oriented variant of ``onStoreDidLoad`` to perform any additional logic when the ``Store`` has finished loading. Patterns like MVVM choose to isolate this logic ViewModel, and you can still choose to do that, but exposing this method on a View provides more flexibility to work with your preferred architecture. In the example below we will filter the items in a ``Store`` based on some criteria, to display only the relevant items in our View.

```swift
.onStoreDidLoad(
self.$items,
onLoad: {
self.items = self.filteredItems(self.items)
},
onError: { error in
log.error("Failed to load items", error)
}
)
```

## Further Exploration, @Stored And More

Building an app using the ``Store`` can be really powerful because it leans into SwiftUI's state-driven architecture, while providing you with offline-first capabilities, realtime updates across your app, with almost no additional code required.
Expand Down
12 changes: 12 additions & 0 deletions Sources/Boutique/Store+Observation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import SwiftUI

public extension View {
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
/// Observes whether a ``Store`` has finished loading it's items.
///
/// - Parameters:
/// - store: The ``Store`` whose loading is being observed.
/// - onLoad: The closure to call when the ``Store`` has finished loading.
/// - onError: The closure to call if an error occurs and the ``Store`` is not loaded.
func onStoreDidLoad<StorableItem: Codable & Sendable>(_ store: Store<StorableItem>, onLoad: @escaping () -> Void, onError: ((Error) -> Void)? = nil) -> some View {
self.task({
do {
Expand All @@ -14,6 +20,12 @@ public extension View {
}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
/// Observes whether a ``Store`` has finished loading it's items.
///
/// - Parameters:
/// - store: The ``Store`` whose loading is being observed.
/// - update: A binding to that will be updated when the ``Store`` has finished loading.
/// - onError: The closure to call if an error occurs and the ``Store`` is not loaded.
func onStoreDidLoad<StorableItem: Codable & Sendable>(_ store: Store<StorableItem>, update hasLoadedState: Binding<Bool>, onError: ((Error) -> Void)? = nil) -> some View {
self.task({
do {
Expand Down

0 comments on commit 2baca64

Please sign in to comment.