Skip to content

Commit e7b1b0f

Browse files
committed
feat: update package and improve demo
1 parent abca0c9 commit e7b1b0f

File tree

10 files changed

+452
-173
lines changed

10 files changed

+452
-173
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.0.0-Beta.8
4+
5+
* Update underlying package which has a fix to avoid `watchQuery` race conditions
6+
37
## 1.0.0-Beta.7
48

59
* Fixed an issue where throwing exceptions in the query `mapper` could cause a runtime crash.

Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Demo/PowerSyncExample/Components/AddTodoListView.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,41 @@ import SwiftUI
33

44
struct AddTodoListView: View {
55
@Environment(SystemManager.self) private var system
6+
@State private var isLoading = false
67

78
@Binding var newTodo: NewTodo
89
let listId: String
910
let completion: (Result<Bool, Error>) -> Void
10-
11+
1112
var body: some View {
1213
Section {
1314
TextField("Description", text: $newTodo.description)
14-
Button("Save") {
15-
Task{
15+
16+
Button {
17+
Task {
18+
isLoading = true
19+
defer { isLoading = false }
20+
1621
do {
1722
try await system.insertTodo(newTodo, listId)
18-
await completion(.success(true))
23+
completion(.success(true))
1924
} catch {
20-
await completion(.failure(error))
25+
completion(.failure(error))
2126
throw error
2227
}
2328
}
29+
} label: {
30+
HStack {
31+
Text("Save")
32+
if isLoading {
33+
Spacer()
34+
ProgressView()
35+
.progressViewStyle(CircularProgressViewStyle())
36+
}
37+
}
38+
.frame(maxWidth: .infinity, alignment: .leading)
2439
}
40+
.disabled(isLoading)
2541
}
2642
}
2743
}

Demo/PowerSyncExample/Components/TodoListView.swift

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,69 +10,140 @@ struct TodoListView: View {
1010
@State private var error: Error?
1111
@State private var newTodo: NewTodo?
1212
@State private var editing: Bool = false
13+
@State private var isLoadingTodos: Bool = false
14+
@State private var batchInsertProgress: Double? = nil
1315

1416
var body: some View {
15-
List {
16-
if let error {
17-
ErrorText(error)
18-
}
17+
ZStack {
18+
List {
19+
if let error {
20+
ErrorText(error)
21+
}
1922

20-
IfLet($newTodo) { $newTodo in
21-
AddTodoListView(newTodo: $newTodo, listId: listId) { result in
22-
withAnimation {
23-
self.newTodo = nil
23+
IfLet($newTodo) { $newTodo in
24+
AddTodoListView(newTodo: $newTodo, listId: listId) { result in
25+
withAnimation {
26+
self.newTodo = nil
27+
}
2428
}
2529
}
26-
}
2730

28-
ForEach(todos) { todo in
29-
TodoListRow(todo: todo) {
30-
Task {
31-
try await toggleCompletion(of: todo)
31+
if let progress = batchInsertProgress {
32+
Section {
33+
VStack(alignment: .leading, spacing: 8) {
34+
Text("Inserting todos...")
35+
.font(.subheadline)
36+
.foregroundColor(.secondary)
37+
38+
ProgressView(value: progress)
39+
.progressViewStyle(LinearProgressViewStyle())
40+
41+
Text("\(Int(progress * 100))% complete")
42+
.font(.caption)
43+
.foregroundColor(.secondary)
44+
}
45+
.padding(.vertical, 8)
3246
}
3347
}
34-
}
35-
.onDelete { indexSet in
36-
Task {
37-
await delete(at: indexSet)
48+
49+
ForEach(todos) { todo in
50+
TodoListRow(todo: todo) {
51+
Task {
52+
try await toggleCompletion(of: todo)
53+
}
54+
}
55+
}
56+
.onDelete { indexSet in
57+
Task {
58+
await delete(at: indexSet)
59+
}
3860
}
3961
}
40-
}
41-
.animation(.default, value: todos)
42-
.navigationTitle("Todos")
43-
.toolbar {
44-
ToolbarItem(placement: .primaryAction) {
45-
if (newTodo == nil) {
46-
Button {
47-
withAnimation {
48-
newTodo = .init(
49-
listId: listId,
50-
isComplete: false,
51-
description: ""
52-
)
62+
.animation(.default, value: todos)
63+
.animation(.default, value: batchInsertProgress)
64+
.navigationTitle("Todos")
65+
.toolbar {
66+
ToolbarItem(placement: .primaryAction) {
67+
if batchInsertProgress != nil {
68+
// Show nothing while batch inserting
69+
EmptyView()
70+
} else if (newTodo == nil) {
71+
Menu {
72+
Button {
73+
withAnimation {
74+
newTodo = .init(
75+
listId: listId,
76+
isComplete: false,
77+
description: ""
78+
)
79+
}
80+
} label: {
81+
Label("Add Single Todo", systemImage: "plus")
82+
}
83+
84+
Button {
85+
Task {
86+
withAnimation {
87+
batchInsertProgress = 0
88+
}
89+
90+
do {
91+
try await system.insertManyTodos(listId: listId) { progress in
92+
withAnimation {
93+
batchInsertProgress = progress
94+
if progress >= 1.0 {
95+
// Small delay to show 100% before hiding
96+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
97+
withAnimation {
98+
batchInsertProgress = nil
99+
}
100+
}
101+
}
102+
}
103+
}
104+
} catch {
105+
self.error = error
106+
withAnimation {
107+
batchInsertProgress = nil
108+
}
109+
}
110+
}
111+
} label: {
112+
Label("Add Many Todos", systemImage: "plus.square.on.square")
113+
}
114+
} label: {
115+
Label("Add", systemImage: "plus")
53116
}
54-
} label: {
55-
Label("Add", systemImage: "plus")
56-
}
57-
} else {
58-
Button("Cancel", role: .cancel) {
59-
withAnimation {
60-
newTodo = nil
117+
} else {
118+
Button("Cancel", role: .cancel) {
119+
withAnimation {
120+
newTodo = nil
121+
}
61122
}
62123
}
63124
}
64125
}
126+
127+
if isLoadingTodos && todos.isEmpty {
128+
ProgressView()
129+
.progressViewStyle(CircularProgressViewStyle())
130+
.scaleEffect(1.5)
131+
.frame(maxWidth: .infinity, maxHeight: .infinity)
132+
.background(Color.black.opacity(0.05))
133+
}
65134
}
66135
.task {
136+
isLoadingTodos = true
67137
await system.watchTodos(listId) { tds in
68138
withAnimation {
69139
self.todos = IdentifiedArrayOf(uniqueElements: tds)
140+
self.isLoadingTodos = false
70141
}
71142
}
72143
}
73144
}
74145

75-
func toggleCompletion(of todo: Todo) async {
146+
func toggleCompletion(of todo: Todo) async throws {
76147
var updatedTodo = todo
77148
updatedTodo.isComplete.toggle()
78149
do {
@@ -89,7 +160,6 @@ struct TodoListView: View {
89160
let todosToDelete = offset.map { todos[$0] }
90161

91162
try await system.deleteTodo(id: todosToDelete[0].id)
92-
93163
} catch {
94164
self.error = error
95165
}

Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -81,36 +81,37 @@ class SupabaseConnector: PowerSyncBackendConnector {
8181
}
8282

8383
override func uploadData(database: PowerSyncDatabaseProtocol) async throws {
84+
guard let transaction = try await database.getCrudBatch() else { return }
85+
86+
var lastEntry: CrudEntry?
87+
do {
88+
try await Task.detached {
89+
for entry in transaction.crud {
90+
lastEntry = entry
91+
let tableName = entry.table
92+
93+
let table = self.client.from(tableName)
94+
95+
switch entry.op {
96+
case .put:
97+
var data: [String: AnyCodable] = entry.opData?.mapValues { AnyCodable($0) } ?? [:]
98+
data["id"] = AnyCodable(entry.id)
99+
try await table.upsert(data).execute();
100+
case .patch:
101+
guard let opData = entry.opData else { continue }
102+
let encodableData = opData.mapValues { AnyCodable($0) }
103+
try await table.update(encodableData).eq("id", value: entry.id).execute()
104+
case .delete:
105+
try await table.delete().eq( "id", value: entry.id).execute()
106+
}
107+
}
84108

85-
guard let transaction = try await database.getNextCrudTransaction() else { return }
86-
87-
var lastEntry: CrudEntry?
88-
do {
89-
for entry in transaction.crud {
90-
lastEntry = entry
91-
let tableName = entry.table
92-
93-
let table = client.from(tableName)
94-
95-
switch entry.op {
96-
case .put:
97-
var data: [String: AnyCodable] = entry.opData?.mapValues { AnyCodable($0) } ?? [:]
98-
data["id"] = AnyCodable(entry.id)
99-
try await table.upsert(data).execute();
100-
case .patch:
101-
guard let opData = entry.opData else { continue }
102-
let encodableData = opData.mapValues { AnyCodable($0) }
103-
try await table.update(encodableData).eq("id", value: entry.id).execute()
104-
case .delete:
105-
try await table.delete().eq( "id", value: entry.id).execute()
106-
}
107-
}
108-
109-
_ = try await transaction.complete.invoke(p1: nil)
109+
_ = try await transaction.complete.invoke(p1: nil)
110+
}.value
110111

111-
} catch {
112-
if let errorCode = PostgresFatalCodes.extractErrorCode(from: error),
113-
PostgresFatalCodes.isFatalError(errorCode) {
112+
} catch {
113+
if let errorCode = PostgresFatalCodes.extractErrorCode(from: error),
114+
PostgresFatalCodes.isFatalError(errorCode) {
114115
/// Instead of blocking the queue with these errors,
115116
/// discard the (rest of the) transaction.
116117
///
@@ -123,9 +124,9 @@ class SupabaseConnector: PowerSyncBackendConnector {
123124
return
124125
}
125126

126-
print("Data upload error - retrying last entry: \(lastEntry!), \(error)")
127-
throw error
128-
}
127+
print("Data upload error - retrying last entry: \(lastEntry!), \(error)")
128+
throw error
129+
}
129130
}
130131

131132
deinit {

0 commit comments

Comments
 (0)