diff --git a/packages/core/lib/plugins/segment_destination.dart b/packages/core/lib/plugins/segment_destination.dart index 501d213..e211ba1 100644 --- a/packages/core/lib/plugins/segment_destination.dart +++ b/packages/core/lib/plugins/segment_destination.dart @@ -30,22 +30,31 @@ class SegmentDestination extends DestinationPlugin with Flushable { final List sentEvents = []; var numFailedEvents = 0; + // FIXED: Only dequeue successfully sent events await Future.forEach(chunkedEvents, (batch) async { try { final succeeded = await analytics?.httpClient.startBatchUpload( analytics!.state.configuration.state.writeKey, batch, host: _apiHost); - if (succeeded == null || !succeeded) { + + if (succeeded == true) { + // Only add to sentEvents on actual success + sentEvents.addAll(batch); + } else { numFailedEvents += batch.length; } - sentEvents.addAll(batch); } catch (e) { numFailedEvents += batch.length; - } finally { - _queuePlugin.dequeue(sentEvents); + // Don't add failed events to sentEvents } + // Move dequeue outside finally, after the loop }); + // Only dequeue events that were actually sent successfully + if (sentEvents.isNotEmpty) { + _queuePlugin.dequeue(sentEvents); + } + if (sentEvents.isNotEmpty) { log("Sent ${sentEvents.length} events", kind: LogFilterKind.debug); } diff --git a/packages/core/lib/utils/store/io.dart b/packages/core/lib/utils/store/io.dart index f93b53d..91810f4 100644 --- a/packages/core/lib/utils/store/io.dart +++ b/packages/core/lib/utils/store/io.dart @@ -10,19 +10,28 @@ import 'package:path_provider/path_provider.dart'; class StoreImpl with Store { final bool storageJson; - StoreImpl({this.storageJson = true}); + late final Future _migrationCompleted; + + StoreImpl({this.storageJson = true}) { + // Start migration immediately but don't block construction + _migrationCompleted = _migrateFilesFromDocumentsToSupport(); + } + @override + Future get ready => Future.value(); + @override - Future?> getPersisted(String key) { + Future?> getPersisted(String key) async { if (!storageJson) return Future.value(null); + // Ensure migration is complete before reading files + await _migrationCompleted; return _readFile(key); } @override - Future get ready => Future.value(); - - @override - Future setPersisted(String key, Map value) { + Future setPersisted(String key, Map value) async { if (!storageJson) return Future.value(); + // Ensure migration is complete before writing files + await _migrationCompleted; return _writeFile(key, value); } @@ -70,7 +79,7 @@ class StoreImpl with Store { } Future _fileName(String fileKey) async { - final path = (await _getDocumentDir()).path; + final path = (await _getNewDocumentDir()).path; return "$path/analytics-flutter-$fileKey.json"; } @@ -88,7 +97,15 @@ class StoreImpl with Store { } } - Future _getDocumentDir() async { + Future _getNewDocumentDir() async { + try { + return await getApplicationSupportDirectory(); + } catch (err) { + throw PlatformNotSupportedError(); + } + } + + Future _getOldDocumentDir() async { try { return await getApplicationDocumentsDirectory(); } catch (err) { @@ -96,6 +113,44 @@ class StoreImpl with Store { } } + /// Migrates existing analytics files from Documents directory to Application Support directory + Future _migrateFilesFromDocumentsToSupport() async { + try { + final oldDir = await _getOldDocumentDir(); + final newDir = await _getNewDocumentDir(); + + // List all analytics files in the old directory + final oldDirFiles = oldDir.listSync() + .whereType() + .where((file) => file.path.contains('analytics-flutter-') && file.path.endsWith('.json')) + .toList(); + + for (final oldFile in oldDirFiles) { + final fileName = oldFile.path.split('/').last; + final newFilePath = '${newDir.path}/$fileName'; + final newFile = File(newFilePath); + + // Only migrate if the file doesn't already exist in the new location + if (!await newFile.exists()) { + try { + // Ensure the new directory exists + await newDir.create(recursive: true); + + // Copy the file to the new location + await oldFile.copy(newFilePath); + + // Delete the old file after successful copy + await oldFile.delete(); + } catch (e) { + // The app should continue to work even if migration fails + } + } + } + } catch (e) { + // Migration failure shouldn't break the app + } + } + @override void dispose() {} }