From 9b4eb29eb491038e908943143acdc7d94c914031 Mon Sep 17 00:00:00 2001 From: Hossam Eldin Mahmoud Date: Fri, 24 Oct 2025 00:08:35 +0300 Subject: [PATCH 1/3] implement exists and delete, unit tests --- packages/cross_file/lib/src/types/base.dart | 10 ++++++ packages/cross_file/lib/src/types/html.dart | 24 +++++++++++++ packages/cross_file/lib/src/types/io.dart | 14 ++++++++ .../cross_file/test/x_file_html_test.dart | 34 +++++++++++++++++++ packages/cross_file/test/x_file_io_test.dart | 31 +++++++++++++++++ 5 files changed, 113 insertions(+) diff --git a/packages/cross_file/lib/src/types/base.dart b/packages/cross_file/lib/src/types/base.dart index b3fb82ea406..4044fae3c81 100644 --- a/packages/cross_file/lib/src/types/base.dart +++ b/packages/cross_file/lib/src/types/base.dart @@ -86,4 +86,14 @@ abstract class XFileBase { Future lastModified() { throw UnimplementedError('lastModified() has not been implemented.'); } + + /// Check whether the CrossFile exists + Future exists() { + throw UnimplementedError('exists() has not been implemented.'); + } + + /// Delete the CrossFile + Future delete() { + throw UnimplementedError('delete() has not been implemented.'); + } } diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart index e8d8371f062..cf968dbcaf6 100644 --- a/packages/cross_file/lib/src/types/html.dart +++ b/packages/cross_file/lib/src/types/html.dart @@ -176,6 +176,30 @@ class XFile extends XFileBase { return readAsBytes().then(encoding.decode); } + @override + Future exists() { + return _blob + .then((Blob blob) { + return blob.size > 0; + }) + .catchError((Object _) { + return false; + }); + } + + @override + Future delete() { + // On web, deleting a file is not possible. + // However, we can revoke the ObjectUrl to free up memory. + if (_browserBlob != null) { + URL.revokeObjectURL(_path); + _browserBlob = null; + return Future.value(true); + } else { + return Future.value(false); + } + } + // TODO(dit): https://github.com/flutter/flutter/issues/91867 Implement openRead properly. @override Stream openRead([int? start, int? end]) async* { diff --git a/packages/cross_file/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart index 74ca1806cc3..aa9ed59f960 100644 --- a/packages/cross_file/lib/src/types/io.dart +++ b/packages/cross_file/lib/src/types/io.dart @@ -138,4 +138,18 @@ class XFile extends XFileBase { .map((List chunk) => Uint8List.fromList(chunk)); } } + + @override + // ignore: avoid_slow_async_io + Future exists() => _file.exists(); + + @override + Future delete() async { + try { + await _file.delete(); + return true; + } catch (_) { + return false; + } + } } diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart index 43449035b92..ec611d42a39 100644 --- a/packages/cross_file/test/x_file_html_test.dart +++ b/packages/cross_file/test/x_file_html_test.dart @@ -63,6 +63,23 @@ void main() { test('Stream can be sliced', () async { expect(await file.openRead(2, 5).first, equals(bytes.sublist(2, 5))); }); + + test('exists() returns true when blob exists', () async { + expect(await file.exists(), isTrue); + }); + + test('delete() revokes ObjectURL and returns true', () async { + final XFile fileToDelete = XFile.fromData(bytes); + expect(await fileToDelete.exists(), isTrue); + + final bool deleted = await fileToDelete.delete(); + expect(deleted, isTrue); + + // After delete, the blob should not be accessible + expect(() async { + await fileToDelete.readAsBytes(); + }, throwsException); + }); }); group('Blob backend', () { @@ -85,6 +102,23 @@ void main() { await file.readAsBytes(); }, throwsException); }); + + test('exists() returns false after ObjectURL is revoked', () async { + final XFile fileToRevoke = XFile(textFileUrl); + expect(await fileToRevoke.exists(), isTrue); + + html.URL.revokeObjectURL(fileToRevoke.path); + expect(await fileToRevoke.exists(), isFalse); + }); + + test('delete() returns false when blob is not cached', () async { + final XFile fileWithoutBlob = XFile(textFileUrl); + // Read to ensure we're testing the uncached path + await fileWithoutBlob.readAsBytes(); + + final bool deleted = await fileWithoutBlob.delete(); + expect(deleted, isFalse); + }); }); group('saveTo(..)', () { diff --git a/packages/cross_file/test/x_file_io_test.dart b/packages/cross_file/test/x_file_io_test.dart index a6de36568d1..4639fee53da 100644 --- a/packages/cross_file/test/x_file_io_test.dart +++ b/packages/cross_file/test/x_file_io_test.dart @@ -75,6 +75,37 @@ void main() { test('nullability is correct', () async { expect(_ensureNonnullPathArgument('a/path'), isNotNull); }); + + test('exists() returns true for existing file', () async { + final XFile file = XFile(textFilePath); + expect(await file.exists(), isTrue); + }); + + test('exists() returns false for non-existing file', () async { + final XFile file = XFile('/non/existent/path.txt'); + expect(await file.exists(), isFalse); + }); + + test('delete() removes file and returns true', () async { + final Directory tempDir = Directory.systemTemp.createTempSync(); + final File targetFile = File('${tempDir.path}/deleteMe.txt'); + targetFile.writeAsStringSync('Delete this'); + + final XFile file = XFile(targetFile.path); + expect(await file.exists(), isTrue); + + final bool deleted = await file.delete(); + expect(deleted, isTrue); + expect(targetFile.existsSync(), isFalse); + + await tempDir.delete(recursive: true); + }); + + test('delete() returns false for non-existing file', () async { + final XFile file = XFile('/non/existent/path.txt'); + final bool deleted = await file.delete(); + expect(deleted, isFalse); + }); }); group('Create with data', () { From 60077c13531712fab83e3eb6afd2ee08d54f4bdf Mon Sep 17 00:00:00 2001 From: Hossam Eldin Mahmoud Date: Fri, 24 Oct 2025 00:51:01 +0300 Subject: [PATCH 2/3] [Version][0.3.5+3] version update --- packages/cross_file/CHANGELOG.md | 5 +++++ packages/cross_file/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index 8db113b687e..6d0d5fca033 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -2,6 +2,11 @@ * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. +## 0.3.5+3 + +* Adds `exists()` implementation. +* Adds `delete()` implementation. + ## 0.3.4+2 * Adds support for `web: ^1.0.0`. diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml index 13e5474fbf9..4a70eef0791 100644 --- a/packages/cross_file/pubspec.yaml +++ b/packages/cross_file/pubspec.yaml @@ -2,7 +2,7 @@ name: cross_file description: An abstraction to allow working with files across multiple platforms. repository: https://github.com/flutter/packages/tree/main/packages/cross_file issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 -version: 0.3.4+2 +version: 0.3.5+3 environment: sdk: ^3.7.0 From 70ef3370a25e3af04515831e90b9451d2da5e50e Mon Sep 17 00:00:00 2001 From: Hossam Eldin Mahmoud Date: Fri, 24 Oct 2025 01:28:27 +0300 Subject: [PATCH 3/3] fix pr comments --- packages/cross_file/lib/src/types/html.dart | 22 ++++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart index cf968dbcaf6..3a80dec3cb7 100644 --- a/packages/cross_file/lib/src/types/html.dart +++ b/packages/cross_file/lib/src/types/html.dart @@ -177,27 +177,25 @@ class XFile extends XFileBase { } @override - Future exists() { - return _blob - .then((Blob blob) { - return blob.size > 0; - }) - .catchError((Object _) { - return false; - }); + Future exists() async { + try { + await _blob; + return true; + } catch (_) { + return false; + } } @override - Future delete() { + Future delete() async { // On web, deleting a file is not possible. // However, we can revoke the ObjectUrl to free up memory. if (_browserBlob != null) { URL.revokeObjectURL(_path); _browserBlob = null; - return Future.value(true); - } else { - return Future.value(false); + return true; } + return false; } // TODO(dit): https://github.com/flutter/flutter/issues/91867 Implement openRead properly.