diff --git a/demos/supabase-simple-chat/lib/pages/login_page.dart b/demos/supabase-simple-chat/lib/pages/login_page.dart index b973f09f..2d62f6ef 100644 --- a/demos/supabase-simple-chat/lib/pages/login_page.dart +++ b/demos/supabase-simple-chat/lib/pages/login_page.dart @@ -37,7 +37,7 @@ class LoginPageState extends State { } catch (_) { context.showErrorSnackBar(message: unexpectedErrorMessage); } - if (mounted) { + if (context.mounted) { setState(() { _isLoading = true; }); diff --git a/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj b/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj index d274f929..16636b7a 100644 --- a/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj +++ b/demos/supabase-todolist/ios/Runner.xcodeproj/project.pbxproj @@ -155,7 +155,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a6b826db..5e31d3d3 100644 --- a/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/demos/supabase-todolist/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { @@ -38,7 +41,17 @@ class _PhotoWidgetState extends State { bool fileExists = await File(photoPath).exists(); - return _ResolvedPhotoState(photoPath: photoPath, fileExists: fileExists); + final row = await attachmentQueue.db + .getOptional('SELECT * FROM attachments_queue WHERE id = ?', [photoId]); + + if (row != null) { + Attachment attachment = Attachment.fromRow(row); + return _ResolvedPhotoState( + photoPath: photoPath, fileExists: fileExists, attachment: attachment); + } + + return _ResolvedPhotoState( + photoPath: photoPath, fileExists: fileExists, attachment: null); } @override @@ -84,6 +97,20 @@ class _PhotoWidgetState extends State { String? filePath = data.photoPath; bool fileIsDownloading = !data.fileExists; + bool fileArchived = + data.attachment?.state == AttachmentState.archived.index; + + if (fileArchived) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Unavailable"), + const SizedBox(height: 8), + takePhotoButton + ], + ); + } if (fileIsDownloading) { return const Text("Downloading..."); diff --git a/demos/supabase-todolist/lib/attachments/queue.dart b/demos/supabase-todolist/lib/attachments/queue.dart index f18fb2a3..07a4b995 100644 --- a/demos/supabase-todolist/lib/attachments/queue.dart +++ b/demos/supabase-todolist/lib/attachments/queue.dart @@ -11,9 +11,21 @@ import 'package:powersync_flutter_demo/models/schema.dart'; late final PhotoAttachmentQueue attachmentQueue; final remoteStorage = SupabaseStorageAdapter(); +/// Function to handle errors when downloading attachments +/// Return false if you want to archive the attachment +Future onDownloadError(Attachment attachment, Object exception) async { + if (exception.toString().contains('Object not found')) { + return false; + } + return true; +} + class PhotoAttachmentQueue extends AbstractAttachmentQueue { PhotoAttachmentQueue(db, remoteStorage) - : super(db: db, remoteStorage: remoteStorage); + : super( + db: db, + remoteStorage: remoteStorage, + onDownloadError: onDownloadError); @override init() async { diff --git a/demos/supabase-todolist/lib/widgets/lists_page.dart b/demos/supabase-todolist/lib/widgets/lists_page.dart index 933fabc1..e31c2fc8 100644 --- a/demos/supabase-todolist/lib/widgets/lists_page.dart +++ b/demos/supabase-todolist/lib/widgets/lists_page.dart @@ -61,7 +61,7 @@ class _ListsWidgetState extends State { super.initState(); final stream = TodoList.watchListsWithStats(); _subscription = stream.listen((data) { - if (!mounted) { + if (!context.mounted) { return; } setState(() { diff --git a/demos/supabase-todolist/lib/widgets/query_widget.dart b/demos/supabase-todolist/lib/widgets/query_widget.dart index b9a285dd..fed1bd78 100644 --- a/demos/supabase-todolist/lib/widgets/query_widget.dart +++ b/demos/supabase-todolist/lib/widgets/query_widget.dart @@ -46,7 +46,7 @@ class QueryWidgetState extends State { _subscription?.cancel(); final stream = db.watch(_query); _subscription = stream.listen((data) { - if (!mounted) { + if (!context.mounted) { return; } setState(() { diff --git a/demos/supabase-todolist/lib/widgets/todo_list_page.dart b/demos/supabase-todolist/lib/widgets/todo_list_page.dart index e8cebba5..e36e1867 100644 --- a/demos/supabase-todolist/lib/widgets/todo_list_page.dart +++ b/demos/supabase-todolist/lib/widgets/todo_list_page.dart @@ -62,7 +62,7 @@ class TodoListWidgetState extends State { super.initState(); final stream = widget.list.watchItems(); _subscription = stream.listen((data) { - if (!mounted) { + if (!context.mounted) { return; } setState(() { diff --git a/demos/supabase-todolist/pubspec.lock b/demos/supabase-todolist/pubspec.lock index e0615605..a3e502c4 100644 --- a/demos/supabase-todolist/pubspec.lock +++ b/demos/supabase-todolist/pubspec.lock @@ -429,7 +429,7 @@ packages: path: "../../packages/powersync_attachments_helper" relative: true source: path - version: "0.2.0" + version: "0.2.1" realtime_client: dependency: transitive description: diff --git a/packages/powersync_attachments_helper/CHANGELOG.md b/packages/powersync_attachments_helper/CHANGELOG.md index c59f0d09..4f8ebdf8 100644 --- a/packages/powersync_attachments_helper/CHANGELOG.md +++ b/packages/powersync_attachments_helper/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.2.1 + +- Added `onUploadError` as an optional function that can be set when setting up the queue to handle upload errors +- Added `onDownloadError` as an optional function that can be set when setting up the queue to handle upload errors + ## 0.2.0 - Potentially BREAKING CHANGE for users who rely on multiple attachment queues. diff --git a/packages/powersync_attachments_helper/lib/src/attachments_queue.dart b/packages/powersync_attachments_helper/lib/src/attachments_queue.dart index 8dcaa0a1..e9e525ab 100644 --- a/packages/powersync_attachments_helper/lib/src/attachments_queue.dart +++ b/packages/powersync_attachments_helper/lib/src/attachments_queue.dart @@ -24,16 +24,28 @@ abstract class AbstractAttachmentQueue { final LocalStorageAdapter localStorage = LocalStorageAdapter(); String attachmentsQueueTableName; + /// Function to handle errors when downloading attachments + /// Return true if you want to ignore attachment + Future Function(Attachment attachment, Object exception)? + onDownloadError; + + /// Function to handle errors when uploading attachments + /// Return true if you want to ignore attachment + Future Function(Attachment attachment, Object exception)? onUploadError; + AbstractAttachmentQueue( {required this.db, required this.remoteStorage, this.attachmentDirectoryName = 'attachments', this.attachmentsQueueTableName = defaultAttachmentsQueueTableName, + this.onDownloadError, + this.onUploadError, performInitialSync = true}) { attachmentsService = AttachmentsService( db, localStorage, attachmentDirectoryName, attachmentsQueueTableName); syncingService = SyncingService( - db, remoteStorage, localStorage, attachmentsService, getLocalUri); + db, remoteStorage, localStorage, attachmentsService, getLocalUri, + onDownloadError: onDownloadError, onUploadError: onUploadError); } /// Create watcher to get list of ID's from a table to be used for syncing in the attachment queue. diff --git a/packages/powersync_attachments_helper/lib/src/attachments_queue_table.dart b/packages/powersync_attachments_helper/lib/src/attachments_queue_table.dart index f1bf5a0d..05570d1f 100644 --- a/packages/powersync_attachments_helper/lib/src/attachments_queue_table.dart +++ b/packages/powersync_attachments_helper/lib/src/attachments_queue_table.dart @@ -64,11 +64,8 @@ class Attachment { /// 1. Attachment to be uploaded /// 2. Attachment to be downloaded /// 3. Attachment to be deleted -enum AttachmentState { - queuedUpload, - queuedDownload, - queuedDelete, -} +/// 4. Attachment to be archived +enum AttachmentState { queuedUpload, queuedDownload, queuedDelete, archived } class AttachmentsQueueTable extends Table { AttachmentsQueueTable( diff --git a/packages/powersync_attachments_helper/lib/src/attachments_service.dart b/packages/powersync_attachments_helper/lib/src/attachments_service.dart index 3c9a3e34..a337ad87 100644 --- a/packages/powersync_attachments_helper/lib/src/attachments_service.dart +++ b/packages/powersync_attachments_helper/lib/src/attachments_service.dart @@ -23,6 +23,11 @@ class AttachmentsService { Future deleteAttachment(String id) async => db.execute('DELETE FROM $table WHERE id = ?', [id]); + ///Set the state of the attachment to ignore. + Future ignoreAttachment(String id) async => db.execute( + 'UPDATE $table SET state = ${AttachmentState.archived.index} WHERE id = ?', + [id]); + /// Get the attachment from the attachment queue using an ID. Future getAttachment(String id) async => db.getOptional('SELECT * FROM $table WHERE id = ?', [id]).then((row) { diff --git a/packages/powersync_attachments_helper/lib/src/syncing_service.dart b/packages/powersync_attachments_helper/lib/src/syncing_service.dart index 595b4643..853a1504 100644 --- a/packages/powersync_attachments_helper/lib/src/syncing_service.dart +++ b/packages/powersync_attachments_helper/lib/src/syncing_service.dart @@ -15,13 +15,18 @@ class SyncingService { final LocalStorageAdapter localStorage; final AttachmentsService attachmentsService; final Function getLocalUri; + final Future Function(Attachment attachment, Object exception)? + onDownloadError; + final Future Function(Attachment attachment, Object exception)? + onUploadError; SyncingService(this.db, this.remoteStorage, this.localStorage, - this.attachmentsService, this.getLocalUri); + this.attachmentsService, this.getLocalUri, + {this.onDownloadError, this.onUploadError}); /// Upload attachment from local storage and to remote storage /// then remove it from the queue. - /// If duplicate of the file is found uploading is ignored and + /// If duplicate of the file is found uploading is archived and /// the attachment is removed from the queue. Future uploadAttachment(Attachment attachment) async { if (attachment.localUri == null) { @@ -44,6 +49,14 @@ class SyncingService { } log.severe('Upload attachment error for attachment $attachment', e); + if (onUploadError != null) { + bool shouldRetry = await onUploadError!(attachment, e); + if (!shouldRetry) { + log.info('Attachment with ID ${attachment.id} has been archived', e); + await attachmentsService.ignoreAttachment(attachment.id); + } + } + return; } } @@ -63,6 +76,15 @@ class SyncingService { await attachmentsService.deleteAttachment(attachment.id); return; } catch (e) { + if (onDownloadError != null) { + bool shouldRetry = await onDownloadError!(attachment, e); + if (!shouldRetry) { + log.info('Attachment with ID ${attachment.id} has been archived', e); + await attachmentsService.ignoreAttachment(attachment.id); + return; + } + } + log.severe('Download attachment error for attachment $attachment', e); return; } diff --git a/packages/powersync_attachments_helper/pubspec.yaml b/packages/powersync_attachments_helper/pubspec.yaml index 9656994d..b216afae 100644 --- a/packages/powersync_attachments_helper/pubspec.yaml +++ b/packages/powersync_attachments_helper/pubspec.yaml @@ -1,6 +1,6 @@ name: powersync_attachments_helper description: A helper library for handling attachments when using PowerSync. -version: 0.2.0 +version: 0.2.1 repository: https://github.com/powersync-ja/powersync.dart homepage: https://www.powersync.com/ environment: