-
Notifications
You must be signed in to change notification settings - Fork 30
Closed
Description
I am having problems with the initialization of PowerSync on my Flutter app build in Flutterflow using Supabase.
I get these errors:





This is my frontend init script:
// Automatic FlutterFlow imports
import '/backend/schema/structs/index.dart';
import '/backend/schema/enums/enums.dart';
import '/backend/supabase/supabase.dart';
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'index.dart'; // Imports other custom actions
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart';
import 'package:powersync/powersync.dart' as powersync;
import 'package:supabase_flutter/supabase_flutter.dart';
import 'dart:async';
/**************************************************************
POWERSYNC SETUP INSTRUCTIONS
// Paste your PowerSync Client Schema here.
// We recommend generating this from the dashboard using the "Generate client-side schema" action
// See docs https://docs.powersync.com/usage/tools/powersync-dashboard#actions
// NB: You need to prefix all Schema, powersync.Table, powersync.Column and Index calls with "powersync." due to a FF limitation
**************************************************************/
const powersync.Schema schema = powersync.Schema([
powersync.Table('tbl_job', [
powersync.Column.text('created_at'),
powersync.Column.text('created_by'),
powersync.Column.integer('reference'),
powersync.Column.text('title'),
powersync.Column.text('site_id'),
powersync.Column.text('company_id'),
powersync.Column.text('contact_id'),
powersync.Column.text('purchase_order'),
powersync.Column.real('value')
]),
powersync.Table('tbl_report', [
powersync.Column.text('created_at'),
powersync.Column.text('created_by'),
powersync.Column.text('job_id'),
powersync.Column.text('report_typet_id'),
powersync.Column.text('extent_of_survey'),
powersync.Column.text('hs_info'),
powersync.Column.text('due_date'),
powersync.Column.text('filename'),
powersync.Column.integer('version'),
powersync.Column.text('version_description'),
powersync.Column.text('report_url'),
powersync.Column.text('distribution_list'),
powersync.Column.text('report_prepared_by'),
powersync.Column.text('report_approved_by'),
powersync.Column.integer('task_sitework'),
powersync.Column.integer('task_lab'),
powersync.Column.integer('task_plans'),
powersync.Column.integer('task_approved'),
powersync.Column.integer('task_issued'),
powersync.Column.text('site_info'),
powersync.Column.text('allowances'),
powersync.Column.text('special_instructions')
]),
powersync.Table('tbl_report_type',
[powersync.Column.text('title'), powersync.Column.text('app_template')]),
powersync.Table('tbl_event', [
powersync.Column.text('created_at'),
powersync.Column.text('created_by'),
powersync.Column.text('event_type_id'),
powersync.Column.text('assigned_to_id'),
powersync.Column.text('start_datetime'),
powersync.Column.text('end_datetime'),
powersync.Column.text('report_id'),
powersync.Column.text('title'),
powersync.Column.text('comments'),
powersync.Column.real('revenue')
]),
powersync.Table('tbl_list', [
powersync.Column.text('tenant_id'),
powersync.Column.text('list_name'),
powersync.Column.text('company_id'),
powersync.Column.text('created_at')
]),
powersync.Table('tbl_listitem', [
powersync.Column.text('list_id'),
powersync.Column.text('text_value'),
powersync.Column.integer('int_value'),
powersync.Column.real('float_value'),
powersync.Column.integer('custom_order'),
powersync.Column.text('created_at')
]),
powersync.Table('tbl_site', [
powersync.Column.text('created_at'),
powersync.Column.text('uprn'),
powersync.Column.text('site_name'),
powersync.Column.text('address'),
powersync.Column.text('postcode'),
powersync.Column.text('company_id'),
powersync.Column.text('building_site_id'),
powersync.Column.integer('asbestos_freq'),
powersync.Column.text('asb_date'),
powersync.Column.text('created_by')
]),
powersync.Table('tbl_note', [
powersync.Column.text('created_at'),
powersync.Column.text('created_by'),
powersync.Column.text('title'),
powersync.Column.text('note'),
powersync.Column.text('report_id'),
powersync.Column.integer('archived')
]),
powersync.Table('tbl_attachment', [
powersync.Column.text('created_at'),
powersync.Column.text('created_by'),
powersync.Column.text('type'),
powersync.Column.text('title'),
powersync.Column.text('image_urls'),
powersync.Column.integer('page_number'),
powersync.Column.text('report_id'),
powersync.Column.text('paper_size')
]),
powersync.Table('tbl_asbestositem', [
powersync.Column.text('created_at'),
powersync.Column.text('created_by'),
powersync.Column.text('building_id'),
powersync.Column.integer('register_no'),
powersync.Column.text('site_id'),
powersync.Column.text('location_id'),
powersync.Column.text('item_description'),
powersync.Column.text('reference'),
powersync.Column.text('product_type_id'),
powersync.Column.integer('mra_product_score'),
powersync.Column.integer('mra_condition_score'),
powersync.Column.integer('mra_st_score'),
powersync.Column.integer('mra_analysis_score'),
powersync.Column.text('sample_id'),
powersync.Column.text('analysis_result'),
powersync.Column.integer('pra_required'),
powersync.Column.integer('pra1'),
powersync.Column.integer('pra2'),
powersync.Column.integer('pra3'),
powersync.Column.integer('pra4'),
powersync.Column.integer('pra5'),
powersync.Column.integer('pra6'),
powersync.Column.integer('pra7'),
powersync.Column.integer('pra8'),
powersync.Column.integer('pra9'),
powersync.Column.integer('pra10'),
powersync.Column.text('recommendation_id'),
powersync.Column.text('image_url'),
powersync.Column.integer('archived'),
powersync.Column.text('notes'),
powersync.Column.text('extent'),
powersync.Column.text('unit'),
powersync.Column.integer('accessible'),
powersync.Column.text('approach')
]),
powersync.Table('tbl_asbestoslocation', [
powersync.Column.text('created_at'),
powersync.Column.text('building_id'),
powersync.Column.text('reference'),
powersync.Column.text('location_name'),
powersync.Column.integer('accessed'),
powersync.Column.text('notes'),
powersync.Column.text('image_url'),
powersync.Column.text('floor_level'),
powersync.Column.text('created_by'),
powersync.Column.text('parent_id'),
powersync.Column.text('report_id')
])
]);
/**************************************************************
END POWERSYNC SETUP
**************************************************************/
const PowerSyncEndpoint = FFAppConstants.PowerSyncUrl;
late powersync.PowerSyncDatabase db;
//create one of these for each of your watch() queries
StreamSubscription listsSubscription = Stream<void>.empty().listen((event) {});
const bool kIsWeb = bool.fromEnvironment('dart.library.js_util');
/// Postgres Response codes that we cannot recover from by retrying.
final List<RegExp> fatalResponseCodes = [
// Class 22 — Data Exception
// Examples include data type mismatch.
RegExp(r'^22...$'),
// Class 23 — Integrity Constraint Violation.
// Examples include NOT NULL, FOREIGN KEY and UNIQUE violations.
RegExp(r'^23...$'),
// INSUFFICIENT PRIVILEGE - typically a row-level security violation
RegExp(r'^42501$'),
];
class SupabaseConnector extends powersync.PowerSyncBackendConnector {
powersync.PowerSyncDatabase db;
Future<void>? _refreshFuture;
SupabaseConnector(this.db);
/// Get a Supabase token to authenticate against the PowerSync instance.
@override
Future<powersync.PowerSyncCredentials?> fetchCredentials() async {
// Wait for pending session refresh if any
await _refreshFuture;
// Use Supabase token for PowerSync
final session = Supabase.instance.client.auth.currentSession;
if (session == null) {
// Not logged in
return null;
}
// Use the access token to authenticate against PowerSync
final token = session.accessToken;
// userId and expiresAt are for debugging purposes only
final userId = session.user.id;
final expiresAt = session.expiresAt == null
? null
: DateTime.fromMillisecondsSinceEpoch(session.expiresAt! * 1000);
return powersync.PowerSyncCredentials(
endpoint: PowerSyncEndpoint,
token: token,
userId: userId,
expiresAt: expiresAt);
}
@override
void invalidateCredentials() {
// Trigger a session refresh if auth fails on PowerSync.
// Generally, sessions should be refreshed automatically by Supabase.
// However, in some cases it can be a while before the session refresh is
// retried. We attempt to trigger the refresh as soon as we get an auth
// failure on PowerSync.
//
// This could happen if the device was offline for a while and the session
// expired, and nothing else attempt to use the session it in the meantime.
//
// Timeout the refresh call to avoid waiting for long retries,
// and ignore any errors. Errors will surface as expired tokens.
_refreshFuture = Supabase.instance.client.auth
.refreshSession()
.timeout(const Duration(seconds: 5))
.then((response) => null, onError: (error) => null);
}
// Upload pending changes to Supabase.
@override
Future<void> uploadData(powersync.PowerSyncDatabase database) async {
// This function is called whenever there is data to upload, whether the
// device is online or offline.
// If this call throws an error, it is retried periodically.
final transaction = await database.getNextCrudTransaction();
if (transaction == null) {
return;
}
final rest = Supabase.instance.client.rest;
powersync.CrudEntry? lastOp;
try {
// Note: If transactional consistency is important, use database functions
// or edge functions to process the entire transaction in a single call.
for (var op in transaction.crud) {
lastOp = op;
final table = rest.from(op.table);
if (op.op == powersync.UpdateType.put) {
var data = Map<String, dynamic>.of(op.opData!);
data['id'] = op.id;
await table.upsert(data);
} else if (op.op == powersync.UpdateType.patch) {
await table.update(op.opData!).eq('id', op.id);
} else if (op.op == powersync.UpdateType.delete) {
await table.delete().eq('id', op.id);
}
}
// All operations successful.
await transaction.complete();
} on PostgrestException catch (e) {
if (e.code != null &&
fatalResponseCodes.any((re) => re.hasMatch(e.code!))) {
/// Instead of blocking the queue with these errors,
/// discard the (rest of the) transaction.
///
/// Note that these errors typically indicate a bug in the application.
/// If protecting against data loss is important, save the failing records
/// elsewhere instead of discarding, and/or notify the user.
print('Data upload error - discarding $lastOp' + e.toString());
await transaction.complete();
} else {
// Error may be retryable - e.g. network error or temporary server error.
// Throwing an error here causes this call to be retried after a delay.
rethrow;
}
}
}
}
bool isLoggedIn() {
return Supabase.instance.client.auth.currentSession?.accessToken != null;
}
Future initpowersync() async {
db = powersync.PowerSyncDatabase(
schema: schema, path: await getDatabasePath());
await db.initialize();
SupabaseConnector? currentConnector;
if (isLoggedIn()) {
// If the user is already logged in, connect immediately.
// Otherwise, connect once logged in.
currentConnector = SupabaseConnector(db);
db.connect(connector: currentConnector);
}
Supabase.instance.client.auth.onAuthStateChange.listen((data) async {
final AuthChangeEvent event = data.event;
if (event == AuthChangeEvent.signedIn) {
// Connect to PowerSync when the user is signed in
currentConnector = SupabaseConnector(db);
db.connect(connector: currentConnector!);
} else if (event == AuthChangeEvent.signedOut) {
// Implicit sign out - disconnect, but don't delete data
currentConnector = null;
await db.disconnect();
} else if (event == AuthChangeEvent.tokenRefreshed) {
// Supabase token refreshed - trigger token refresh for PowerSync.
currentConnector?.prefetchCredentials();
}
});
return;
}
Future<String> getDatabasePath() async {
var path = 'powersync-sqlite.db';
// getApplicationSupportDirectory is not supported on Web
if (!kIsWeb) {
final dir = await getApplicationSupportDirectory();
path = join(dir.path, path);
}
return path;
}
There are no logs in the dashboards so I am assuming this must be something on the SDK side.
Thank you for your help.
Metadata
Metadata
Assignees
Labels
No labels