Skip to content

WIP feat: update package and improve demo #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.0.0-Beta.8

* Update underlying package which has a fix to avoid `watchQuery` race conditions

## 1.0.0-Beta.7

* Fixed an issue where throwing exceptions in the query `mapper` could cause a runtime crash.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/powersync-ja/powersync-kotlin.git",
"state" : {
"revision" : "203db74889df8a20e3c6ac38aede6b0186d2e3b5",
"version" : "1.0.0-BETA23.0"
"revision" : "a683c43f9d432fdc9257626659c4da10f67ec8fb",
"version" : "1.0.0-BETA24.0"
}
},
{
Expand Down
26 changes: 21 additions & 5 deletions Demo/PowerSyncExample/Components/AddTodoListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,41 @@ import SwiftUI

struct AddTodoListView: View {
@Environment(SystemManager.self) private var system
@State private var isLoading = false

@Binding var newTodo: NewTodo
let listId: String
let completion: (Result<Bool, Error>) -> Void

var body: some View {
Section {
TextField("Description", text: $newTodo.description)
Button("Save") {
Task{

Button {
Task {
isLoading = true
defer { isLoading = false }

do {
try await system.insertTodo(newTodo, listId)
await completion(.success(true))
completion(.success(true))
} catch {
await completion(.failure(error))
completion(.failure(error))
throw error
}
}
} label: {
HStack {
Text("Save")
if isLoading {
Spacer()
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.disabled(isLoading)
}
}
}
Expand Down
148 changes: 109 additions & 39 deletions Demo/PowerSyncExample/Components/TodoListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,69 +10,140 @@ struct TodoListView: View {
@State private var error: Error?
@State private var newTodo: NewTodo?
@State private var editing: Bool = false
@State private var isLoadingTodos: Bool = false
@State private var batchInsertProgress: Double? = nil

var body: some View {
List {
if let error {
ErrorText(error)
}
ZStack {
List {
if let error {
ErrorText(error)
}

IfLet($newTodo) { $newTodo in
AddTodoListView(newTodo: $newTodo, listId: listId) { result in
withAnimation {
self.newTodo = nil
IfLet($newTodo) { $newTodo in
AddTodoListView(newTodo: $newTodo, listId: listId) { result in
withAnimation {
self.newTodo = nil
}
}
}
}

ForEach(todos) { todo in
TodoListRow(todo: todo) {
Task {
try await toggleCompletion(of: todo)
if let progress = batchInsertProgress {
Section {
VStack(alignment: .leading, spacing: 8) {
Text("Inserting todos...")
.font(.subheadline)
.foregroundColor(.secondary)

ProgressView(value: progress)
.progressViewStyle(LinearProgressViewStyle())

Text("\(Int(progress * 100))% complete")
.font(.caption)
.foregroundColor(.secondary)
}
.padding(.vertical, 8)
}
}
}
.onDelete { indexSet in
Task {
await delete(at: indexSet)

ForEach(todos) { todo in
TodoListRow(todo: todo) {
Task {
try await toggleCompletion(of: todo)
}
}
}
.onDelete { indexSet in
Task {
await delete(at: indexSet)
}
}
}
}
.animation(.default, value: todos)
.navigationTitle("Todos")
.toolbar {
ToolbarItem(placement: .primaryAction) {
if (newTodo == nil) {
Button {
withAnimation {
newTodo = .init(
listId: listId,
isComplete: false,
description: ""
)
.animation(.default, value: todos)
.animation(.default, value: batchInsertProgress)
.navigationTitle("Todos")
.toolbar {
ToolbarItem(placement: .primaryAction) {
if batchInsertProgress != nil {
// Show nothing while batch inserting
EmptyView()
} else if (newTodo == nil) {
Menu {
Button {
withAnimation {
newTodo = .init(
listId: listId,
isComplete: false,
description: ""
)
}
} label: {
Label("Add Single Todo", systemImage: "plus")
}

Button {
Task {
withAnimation {
batchInsertProgress = 0
}

do {
try await system.insertManyTodos(listId: listId) { progress in
withAnimation {
batchInsertProgress = progress
if progress >= 1.0 {
// Small delay to show 100% before hiding
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation {
batchInsertProgress = nil
}
}
}
}
}
} catch {
self.error = error
withAnimation {
batchInsertProgress = nil
}
}
}
} label: {
Label("Add Many Todos", systemImage: "plus.square.on.square")
}
} label: {
Label("Add", systemImage: "plus")
}
} label: {
Label("Add", systemImage: "plus")
}
} else {
Button("Cancel", role: .cancel) {
withAnimation {
newTodo = nil
} else {
Button("Cancel", role: .cancel) {
withAnimation {
newTodo = nil
}
}
}
}
}

if isLoadingTodos && todos.isEmpty {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(1.5)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.black.opacity(0.05))
}
}
.task {
isLoadingTodos = true
await system.watchTodos(listId) { tds in
withAnimation {
self.todos = IdentifiedArrayOf(uniqueElements: tds)
self.isLoadingTodos = false
}
}
}
}

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

try await system.deleteTodo(id: todosToDelete[0].id)

} catch {
self.error = error
}
Expand Down
63 changes: 32 additions & 31 deletions Demo/PowerSyncExample/PowerSync/SupabaseConnector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,36 +81,37 @@ class SupabaseConnector: PowerSyncBackendConnector {
}

override func uploadData(database: PowerSyncDatabaseProtocol) async throws {
guard let transaction = try await database.getCrudBatch() else { return }

var lastEntry: CrudEntry?
do {
try await Task.detached {
for entry in transaction.crud {
lastEntry = entry
let tableName = entry.table

let table = self.client.from(tableName)

switch entry.op {
case .put:
var data: [String: AnyCodable] = entry.opData?.mapValues { AnyCodable($0) } ?? [:]
data["id"] = AnyCodable(entry.id)
try await table.upsert(data).execute();
case .patch:
guard let opData = entry.opData else { continue }
let encodableData = opData.mapValues { AnyCodable($0) }
try await table.update(encodableData).eq("id", value: entry.id).execute()
case .delete:
try await table.delete().eq( "id", value: entry.id).execute()
}
}

guard let transaction = try await database.getNextCrudTransaction() else { return }

var lastEntry: CrudEntry?
do {
for entry in transaction.crud {
lastEntry = entry
let tableName = entry.table

let table = client.from(tableName)

switch entry.op {
case .put:
var data: [String: AnyCodable] = entry.opData?.mapValues { AnyCodable($0) } ?? [:]
data["id"] = AnyCodable(entry.id)
try await table.upsert(data).execute();
case .patch:
guard let opData = entry.opData else { continue }
let encodableData = opData.mapValues { AnyCodable($0) }
try await table.update(encodableData).eq("id", value: entry.id).execute()
case .delete:
try await table.delete().eq( "id", value: entry.id).execute()
}
}

_ = try await transaction.complete.invoke(p1: nil)
_ = try await transaction.complete.invoke(p1: nil)
}.value

} catch {
if let errorCode = PostgresFatalCodes.extractErrorCode(from: error),
PostgresFatalCodes.isFatalError(errorCode) {
} catch {
if let errorCode = PostgresFatalCodes.extractErrorCode(from: error),
PostgresFatalCodes.isFatalError(errorCode) {
/// Instead of blocking the queue with these errors,
/// discard the (rest of the) transaction.
///
Expand All @@ -123,9 +124,9 @@ class SupabaseConnector: PowerSyncBackendConnector {
return
}

print("Data upload error - retrying last entry: \(lastEntry!), \(error)")
throw error
}
print("Data upload error - retrying last entry: \(lastEntry!), \(error)")
throw error
}
}

deinit {
Expand Down
Loading