Using transactions to group changes
Read and write transactions present a context where multiple changes can be made then finally committed to the DB or rolled back. This ensures that either all the changes get persisted, or no change is made to the DB (in the case of a rollback or exception).
// Delete a list and its todos in a transaction
func deleteList(db: PowerSyncDatabase, listId: String) async throws {
try await db.writeTransaction { tx in
try await tx.execute(sql: "DELETE FROM lists WHERE id = ?", parameters: [listId])
try await tx.execute(sql: "DELETE FROM todos WHERE list_id = ?", parameters: [listId])
}
}
Also see readTransaction
.
Subscribe to changes in data
Use watch
to watch for changes to the dependent tables of any SQL query.
// Watch for changes to the lists table
func watchLists(_ callback: @escaping (_ lists: [ListContent]) -> Void ) async {
do {
for try await lists in try self.db.watch<ListContent>(
sql: "SELECT * FROM \(LISTS_TABLE)",
parameters: [],
mapper: { cursor in
try ListContent(
id: cursor.getString(name: "id"),
name: cursor.getString(name: "name"),
createdAt: cursor.getString(name: "created_at"),
ownerId: cursor.getString(name: "owner_id")
)
}
) {
callback(lists)
}
} catch {
print("Error in watch: \(error)")
}
}
Insert, update, and delete data in the local database
Use execute
to run INSERT, UPDATE or DELETE queries.
// Insert a new TODO
func insertTodo(_ todo: NewTodo, _ listId: String) async throws {
try await db.execute(
sql: "INSERT INTO \(TODOS_TABLE) (id, created_at, created_by, description, list_id, completed) VALUES (uuid(), datetime(), ?, ?, ?, ?)",
parameters: [connector.currentUserID, todo.description, listId, todo.isComplete]
)
}
Send changes in local data to your backend service
Override uploadData
to send local updates to your backend service.
class MyConnector: PowerSyncBackendConnector {
override func uploadData(database: PowerSyncDatabaseProtocol) async throws {
let batch = try await database.getCrudBatch()
guard let batch = batch else { return }
for entry in batch.crud {
switch entry.op {
case .put:
// Send the data to your backend service
// Replace `_myApi` with your own API client or service
try await _myApi.put(table: entry.table, data: entry.opData)
default:
// TODO: implement the other operations (patch, delete)
break
}
}
try await batch.complete(writeCheckpoint: nil)
}
}
Use currentStatus
and observe changes to listen for status changes to your PowerSync instance.
import Foundation
import SwiftUI
import PowerSync
struct PowerSyncConnectionIndicator: View {
private let powersync: any PowerSyncDatabaseProtocol
@State private var connected: Bool = false
init(powersync: any PowerSyncDatabaseProtocol) {
self.powersync = powersync
}
var body: some View {
let iconName = connected ? "wifi" : "wifi.slash"
let description = connected ? "Online" : "Offline"
Image(systemName: iconName)
.accessibility(label: Text(description))
.task {
self.connected = powersync.currentStatus.connected
for await status in powersync.currentStatus.asFlow() {
self.connected = status.connected
}
}
}
}
Wait for the initial sync to complete
Use the hasSynced
property and observe status changes to indicate to the user whether the initial sync is in progress.
struct WaitForFirstSync: View {
private let powersync: any PowerSyncDatabaseProtocol
@State var didSync: Bool = false
init(powersync: any PowerSyncDatabaseProtocol) {
self.powersync = powersync
}
var body: some View {
if !didSync {
ProgressView().task {
do {
try await powersync.waitForFirstSync()
} catch {
// TODO: Handle errors
}
}
}
}
}
For async use cases, use waitForFirstSync
.
Report sync download progress
You can show users a progress bar when data downloads using the downloadProgress
property from the SyncStatusData
object. downloadProgress.downloadedFraction
gives you a value from 0.0 to 1.0 representing the total sync progress. This is especially useful for long-running initial syncs.
Example:
struct SyncProgressIndicator: View {
private let powersync: any PowerSyncDatabaseProtocol
private let priority: BucketPriority?
@State private var status: SyncStatusData? = nil
init(powersync: any PowerSyncDatabaseProtocol, priority: BucketPriority? = nil) {
self.powersync = powersync
self.priority = priority
}
var body: some View {
VStack {
if let totalProgress = status?.downloadProgress {
let progress = if let priority = self.priority {
totalProgress.untilPriority(priority: priority)
} else {
totalProgress
}
ProgressView(value: progress.fraction)
if progress.downloadedOperations == progress.totalOperations {
Text("Applying server-side changes...")
} else {
Text("Downloaded \(progress.downloadedOperations) out of \(progress.totalOperations)")
}
}
}.task {
status = powersync.currentStatus
for await status in powersync.currentStatus.asFlow() {
self.status = status
}
}
}
}
Also see:
Responses are generated using AI and may contain mistakes.