mirror of
https://github.com/Dimillian/Skills.git
synced 2026-03-25 08:55:54 +00:00
Add lightweight clients reference and index entry
Introduces a new reference file describing the lightweight, closure-based client pattern for SwiftUI apps. Updates the components index to include a link to this new guidance, supporting easier discovery and usage of small, testable API clients.
This commit is contained in:
parent
1d42087b32
commit
a66c570852
2 changed files with 94 additions and 0 deletions
|
|
@ -26,6 +26,7 @@ Use this file to find component-specific guidance. Each entry lists when to use
|
|||
- Deep links and URL routing: `references/deeplinks.md` — Use for in-app navigation from URLs.
|
||||
- Title menus: `references/title-menus.md` — Use for filter or context menus in the navigation title.
|
||||
- Loading & placeholders: `references/loading-placeholders.md` — Use for redacted skeletons, empty states, and loading UX.
|
||||
- Lightweight clients: `references/lightweight-clients.md` — Use for small, closure-based API clients injected into stores.
|
||||
|
||||
## Planned components (create files as needed)
|
||||
|
||||
|
|
|
|||
93
swiftui-ui-patterns/references/lightweight-clients.md
Normal file
93
swiftui-ui-patterns/references/lightweight-clients.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Lightweight Clients (Closure-Based)
|
||||
|
||||
Use this pattern to keep networking or service dependencies simple and testable without introducing a full view model or heavy DI framework. It works well for SwiftUI apps where you want a small, composable API surface that can be swapped in previews/tests.
|
||||
|
||||
## Intent
|
||||
- Provide a tiny "client" type made of async closures.
|
||||
- Keep business logic in a store or feature layer, not the view.
|
||||
- Enable easy stubbing in previews/tests.
|
||||
|
||||
## Minimal shape
|
||||
```swift
|
||||
struct SomeClient {
|
||||
var fetchItems: (_ limit: Int) async throws -> [Item]
|
||||
var search: (_ query: String, _ limit: Int) async throws -> [Item]
|
||||
}
|
||||
|
||||
extension SomeClient {
|
||||
static func live(baseURL: URL = URL(string: "https://example.com")!) -> SomeClient {
|
||||
let session = URLSession.shared
|
||||
return SomeClient(
|
||||
fetchItems: { limit in
|
||||
// build URL, call session, decode
|
||||
},
|
||||
search: { query, limit in
|
||||
// build URL, call session, decode
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage pattern
|
||||
```swift
|
||||
@MainActor
|
||||
@Observable final class ItemsStore {
|
||||
enum LoadState { case idle, loading, loaded, failed(String) }
|
||||
|
||||
var items: [Item] = []
|
||||
var state: LoadState = .idle
|
||||
private let client: SomeClient
|
||||
|
||||
init(client: SomeClient) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
func load(limit: Int = 20) async {
|
||||
state = .loading
|
||||
do {
|
||||
items = try await client.fetchItems(limit)
|
||||
state = .loaded
|
||||
} catch {
|
||||
state = .failed(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
struct ContentView: View {
|
||||
@Environment(ItemsStore.self) private var store
|
||||
|
||||
var body: some View {
|
||||
List(store.items) { item in
|
||||
Text(item.title)
|
||||
}
|
||||
.task { await store.load() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct MyApp: App {
|
||||
@State private var store = ItemsStore(client: .live())
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
.environment(store)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Guidance
|
||||
- Keep decoding and URL-building in the client; keep state changes in the store.
|
||||
- Make the store accept the client in `init` and keep it private.
|
||||
- Avoid global singletons; use `.environment` for store injection.
|
||||
- If you need multiple variants (mock/stub), add `static func mock(...)`.
|
||||
|
||||
## Pitfalls
|
||||
- Don’t put UI state in the client; keep state in the store.
|
||||
- Don’t capture `self` or view state in the client closures.
|
||||
Loading…
Reference in a new issue