Skip to content

PowerSync Integration

ZyraForm integrates seamlessly with PowerSync for offline-first database operations. Load existing records directly into forms and sync changes automatically.

struct EditUserFormView: View {
@StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema)
@StateObject var service = GenericPowerSyncService(
tableName: "\(AppConfig.dbPrefix)users",
userId: currentUserId
)
let recordId: String
var body: some View {
Form {
// Form fields...
}
.task {
do {
try await form.loadFromPowerSync(
recordId: recordId,
service: service
)
} catch {
print("Failed to load record: \(error)")
}
}
}
}
.task {
do {
try await form.loadFromPowerSync(
recordId: recordId,
tableName: "\(AppConfig.dbPrefix)users",
userId: currentUserId
)
} catch {
print("Failed to load record: \(error)")
}
}
struct EditUserFormViewFromRecord: View {
@StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema)
let record: [String: Any] // Already loaded from PowerSync
var body: some View {
Form {
// Form fields...
}
.onAppear {
form.loadFromRecord(record)
}
}
}

Handle both create and edit scenarios:

struct UserFormView: View {
@StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema)
@StateObject var service = GenericPowerSyncService(
tableName: "\(AppConfig.dbPrefix)users",
userId: currentUserId
)
let recordId: String? // nil for create, String for edit
var isEditing: Bool {
recordId != nil
}
var body: some View {
Form {
Section("User Information") {
if let emailBinding = form.binding(for: "email") {
TextField("Email", text: emailBinding)
.textContentType(.emailAddress)
}
if let nameBinding = form.binding(for: "name") {
TextField("Name", text: nameBinding)
}
}
Section {
Button(action: {
form.handleSubmit { values in
Task {
if isEditing, let id = recordId {
// Update existing record
try await service.updateRecord(id: id, fields: [
"email": values.email,
"name": values.name
])
} else {
// Create new record
let newId = try await service.createRecord(fields: [
"email": values.email,
"name": values.name
])
print("Created record with ID: \(newId)")
}
}
}
}) {
Text(isEditing ? "Save Changes" : "Create User")
.frame(maxWidth: .infinity)
}
.disabled(!form.isValid || form.isSubmitting)
}
}
.navigationTitle(isEditing ? "Edit User" : "New User")
.task {
// Load record if editing
if let id = recordId {
try? await form.loadFromPowerSync(
recordId: id,
service: service
)
}
}
}
}

Load only specific fields:

try await form.loadFromPowerSync(
recordId: recordId,
service: service,
fields: ["email", "name", "age"] // Only load these fields
)

After form submission, update PowerSync:

form.handleSubmit { values in
Task {
do {
let config = UsersSchema.toTableFieldConfig()
try await service.updateRecord(
id: recordId,
fields: [
"email": values.email,
"name": values.name,
"age": values.age ?? NSNull()
],
encryptedFields: config.encryptedFields
)
print("✅ Record updated successfully")
} catch {
print("❌ Failed to update: \(error)")
}
}
}

Create new records from form values:

form.handleSubmit { values in
Task {
do {
let config = UsersSchema.toTableFieldConfig()
let newId = try await service.createRecord(
fields: [
"email": values.email,
"name": values.name,
"age": values.age ?? NSNull()
],
encryptedFields: config.encryptedFields
)
print("✅ Created record with ID: \(newId)")
// Optionally load the new record into the form
try await form.loadFromPowerSync(
recordId: newId,
service: service
)
} catch {
print("❌ Failed to create: \(error)")
}
}
}

Ensure your PowerSync schema includes your tables:

let PowerSyncSchema = Schema(
tables: [
UsersSchema.toPowerSyncTable(),
ProjectsSchema.toPowerSyncTable(),
// ... other tables
]
)
let db = PowerSyncDatabase(
schema: PowerSyncSchema,
dbFilename: "myApp-powersync.sqlite"
)

Load multiple records and populate forms:

struct UserListView: View {
@StateObject var service = GenericPowerSyncService(
tableName: "\(AppConfig.dbPrefix)users",
userId: currentUserId
)
@State private var selectedRecordId: String?
var body: some View {
List {
ForEach(service.records, id: \.self) { record in
Button(action: {
selectedRecordId = record["id"] as? String
}) {
Text(record["name"] as? String ?? "Unknown")
}
}
}
.sheet(item: $selectedRecordId) { id in
EditUserFormView(recordId: id)
}
.task {
let config = UsersSchema.toTableFieldConfig()
try? await service.loadRecords(
fields: config.allFields,
encryptedFields: config.encryptedFields
)
}
}
}

Handle loading errors gracefully:

.task {
do {
try await form.loadFromPowerSync(
recordId: recordId,
service: service
)
} catch {
if let nsError = error as NSError?,
nsError.code == 404 {
// Record not found
print("Record not found")
} else {
// Other error
print("Failed to load: \(error.localizedDescription)")
}
}
}

PowerSync automatically syncs changes to Supabase:

// After updating via form
try await service.updateRecord(id: recordId, fields: [...])
// PowerSync automatically syncs to Supabase
// No manual sync needed!