From 26f75e6f6556c9639983d7d97853d61f4c16e70d Mon Sep 17 00:00:00 2001 From: Yasmany Cubela Medina Date: Tue, 25 Aug 2026 17:15:57 +0000 Subject: [PATCH 1/4] fix(ui): migrate to file_picker 12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit file_picker 12 removed the eager PlatformFile.bytes/.size getters in favour of readAsBytes()/readAsByteStream(), and pickFiles() now returns List instead of a FilePickerResult wrapper. - PlatformFileX.toAttachmentFile becomes async, mirroring the XFileX extension that already sat directly below it in the same file. - Both attachment handlers use pickFile() (singular), which returns the PlatformFile? they already wanted instead of taking .files.first — that also removes a throw on an empty selection. - withData/withReadStream are no longer forwarded (deprecated in 12; the content is now read on demand) but stay in the public signature, so this is not a breaking change for callers. --- .../handler/stream_attachment_handler_html.dart | 15 ++++++++++----- .../handler/stream_attachment_handler_io.dart | 15 ++++++++++----- .../lib/src/utils/extensions.dart | 13 +++++++++---- packages/stream_chat_flutter/pubspec.yaml | 2 +- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart index 92314a8cd1..45ba6e3e91 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart @@ -25,19 +25,24 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { bool withReadStream = false, bool lockParentWindow = true, }) async { - final result = await FilePicker.pickFiles( + // pickFile (singular) since file_picker 12: it returns the `PlatformFile?` + // this method already wanted, instead of a result wrapper we immediately + // took `.files.first` from — which threw on an empty selection. + // + // `withData` / `withReadStream` are no longer forwarded: file_picker 12 + // deprecated them in favour of reading through PlatformFile.readAsBytes() + // / readAsByteStream(), which is what toAttachmentFile now does. They stay + // in this method's signature so this is not a breaking change for callers. + final result = await FilePicker.pickFile( dialogTitle: dialogTitle, initialDirectory: initialDirectory, type: type, allowedExtensions: allowedExtensions, onFileLoading: onFileLoading, compressionQuality: compressionQuality, - withData: withData, - withReadStream: withReadStream, - lockParentWindow: lockParentWindow, ); - return result?.files.first.toAttachment(type: type.toAttachmentType()); + return await result?.toAttachment(type: type.toAttachmentType()); } @override diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart index a3d1bc7ca8..9ff9d8b3f7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart @@ -134,19 +134,24 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { bool withReadStream = false, bool lockParentWindow = true, }) async { - final result = await FilePicker.pickFiles( + // pickFile (singular) since file_picker 12: it returns the `PlatformFile?` + // this method already wanted, instead of a result wrapper we immediately + // took `.files.first` from — which threw on an empty selection. + // + // `withData` / `withReadStream` are no longer forwarded: file_picker 12 + // deprecated them in favour of reading through PlatformFile.readAsBytes() + // / readAsByteStream(), which is what toAttachmentFile now does. They stay + // in this method's signature so this is not a breaking change for callers. + final result = await FilePicker.pickFile( dialogTitle: dialogTitle, initialDirectory: initialDirectory, type: type, allowedExtensions: allowedExtensions, onFileLoading: onFileLoading, compressionQuality: compressionQuality, - withData: withData, - withReadStream: withReadStream, - lockParentWindow: lockParentWindow, ); - return result?.files.first.toAttachment(type: type.toAttachmentType()); + return await result?.toAttachment(type: type.toAttachmentType()); } @override diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 31c92963a8..0661e812f0 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -115,19 +115,24 @@ extension IterableExtension on Iterable { /// Useful extension for [PlatformFile] extension PlatformFileX on PlatformFile { /// Converts the [PlatformFile] into [AttachmentFile] - AttachmentFile get toAttachmentFile { + /// + /// Asynchronous since file_picker 12: `PlatformFile` no longer exposes the + /// eagerly-loaded `bytes`/`size` getters, the content is read on demand. + /// Mirrors [XFileX.toAttachmentFile] below. + Future get toAttachmentFile async { + final bytes = await readAsBytes(); return AttachmentFile( // Path is not supported on web. path: CurrentPlatform.isWeb ? null : path, name: name, + size: bytes.length, bytes: bytes, - size: size, ); } /// Converts the [PlatformFile] to a [Attachment]. - Attachment toAttachment({required String type}) { - final file = toAttachmentFile; + Future toAttachment({required String type}) async { + final file = await toAttachmentFile; final extraDataMap = {}; final mimeType = file.mediaType?.mimeType; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index bba0f2a0f3..a83d9dd771 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -30,7 +30,7 @@ dependencies: diacritic: ^0.1.6 dio: ^5.11.0 ezanimation: ^0.6.0 - file_picker: ^11.0.0 + file_picker: ^12.1.0 file_selector: ^1.1.0 flutter: sdk: flutter From 9c3eeb336a46c751f070bf27cd107a7390b20cb9 Mon Sep 17 00:00:00 2001 From: Yasmany Cubela Medina Date: Tue, 25 Aug 2026 18:21:31 +0000 Subject: [PATCH 2/4] docs(ui): changelog entry for the file_picker 12 migration --- packages/stream_chat_flutter/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 1912e46fca..750bcc0e3c 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -12,9 +12,11 @@ 🔄 Changed - Raised minimum Flutter to `>=3.44.0` and Dart SDK to `^3.12.0`. +- Migrated to `file_picker` 12. `PlatformFileX.toAttachmentFile` and `PlatformFileX.toAttachment` are now asynchronous, matching the existing `XFileX` extensions, because `PlatformFile` no longer exposes the eagerly-loaded `bytes` and `size` getters. `StreamAttachmentHandler.pickFile`'s own signature is unchanged, but `withData` and `withReadStream` are no longer forwarded — `file_picker` deprecated them in favour of reading the content on demand. 🐞 Fixed +- Fixed `StreamAttachmentHandler.pickFile` throwing when the file picker returned an empty selection: it took `.files.first` unconditionally. It now uses `FilePicker.pickFile` and returns `null`. - Fixed a crash on web when the message list rebuilt while messages were selectable, for example after opening the attachment picker. - Fixed the browser's native context menu reappearing over the message context menu on web after scrolling messages out of view or deleting one. - Fixed the SDK re-enabling the browser's native context menu on web in apps that had disabled it themselves. From 3bfd4fdc367c96c5ab5b95bec5347e2a18fd318d Mon Sep 17 00:00:00 2001 From: Yasmany Cubela Medina Date: Wed, 26 Aug 2026 12:36:55 +0000 Subject: [PATCH 3/4] fix(ui): bump file_picker in melos.yaml, restore lockParentWindow melos.yaml is the source of truth for dependency versions, so bootstrap rewrote the package manifest back to file_picker 11 and every job that runs it failed on `pickFile` and `readAsBytes` being undefined. Restores lockParentWindow through WindowsOptions/LinuxOptions, which is where file_picker 12 moved it, drops the migration commentary from the handlers, and calls the async extensions out as breaking in their own changelog section rather than inside the dependency note. --- melos.yaml | 2 +- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++-- .../handler/stream_attachment_handler_html.dart | 10 ++-------- .../handler/stream_attachment_handler_io.dart | 10 ++-------- .../stream_chat_flutter/lib/src/utils/extensions.dart | 4 +--- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/melos.yaml b/melos.yaml index 4172629cd9..a5bb91076f 100644 --- a/melos.yaml +++ b/melos.yaml @@ -52,7 +52,7 @@ command: # upper bounds once FlutterFire ships a fixed release. firebase_crashlytics: '>=5.2.0 <5.2.5' firebase_messaging: '>=16.0.0 <16.4.2' - file_picker: ^11.0.0 + file_picker: ^12.1.0 file_selector: ^1.1.0 app_badge_plus: ^1.3.2 flutter_local_notifications: ^21.0.0 diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 750bcc0e3c..0b99d36790 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -8,15 +8,17 @@ - Long-pressing a reaction chip no longer opens the message actions modal; the chips always claim the long press. Left unset, `onReactionLongPress` defaults to opening the `ReactionDetailSheet`. - Tapping or long-pressing a reaction chip now opens the `ReactionDetailSheet` pre-filtered to that reaction; it previously opened unfiltered. Clustered and overflow chips map to no single reaction, so they still open unfiltered. +- **BREAKING**: `PlatformFileX.toAttachmentFile` and `PlatformFileX.toAttachment` are now asynchronous, returning `Future` and `Future`. `file_picker` 12 removed `PlatformFile`'s eagerly-loaded `bytes` and `size` getters, so the content is read on demand. Both are exported, so callers must `await` them; this matches the existing `XFileX` extensions. 🔄 Changed - Raised minimum Flutter to `>=3.44.0` and Dart SDK to `^3.12.0`. -- Migrated to `file_picker` 12. `PlatformFileX.toAttachmentFile` and `PlatformFileX.toAttachment` are now asynchronous, matching the existing `XFileX` extensions, because `PlatformFile` no longer exposes the eagerly-loaded `bytes` and `size` getters. `StreamAttachmentHandler.pickFile`'s own signature is unchanged, but `withData` and `withReadStream` are no longer forwarded — `file_picker` deprecated them in favour of reading the content on demand. +- Bumped `file_picker` to `^12.1.0`. +- `StreamAttachmentHandler.pickFile` no longer forwards `withData` and `withReadStream`; `file_picker` deprecated them in favour of reading the content on demand. The method's own signature is unchanged. 🐞 Fixed -- Fixed `StreamAttachmentHandler.pickFile` throwing when the file picker returned an empty selection: it took `.files.first` unconditionally. It now uses `FilePicker.pickFile` and returns `null`. +- Fixed `StreamAttachmentHandler.pickFile` throwing when the picker returned an empty selection: it took `.files.first` unconditionally. It now returns `null`. - Fixed a crash on web when the message list rebuilt while messages were selectable, for example after opening the attachment picker. - Fixed the browser's native context menu reappearing over the message context menu on web after scrolling messages out of view or deleting one. - Fixed the SDK re-enabling the browser's native context menu on web in apps that had disabled it themselves. diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart index 45ba6e3e91..1712497431 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart @@ -25,14 +25,6 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { bool withReadStream = false, bool lockParentWindow = true, }) async { - // pickFile (singular) since file_picker 12: it returns the `PlatformFile?` - // this method already wanted, instead of a result wrapper we immediately - // took `.files.first` from — which threw on an empty selection. - // - // `withData` / `withReadStream` are no longer forwarded: file_picker 12 - // deprecated them in favour of reading through PlatformFile.readAsBytes() - // / readAsByteStream(), which is what toAttachmentFile now does. They stay - // in this method's signature so this is not a breaking change for callers. final result = await FilePicker.pickFile( dialogTitle: dialogTitle, initialDirectory: initialDirectory, @@ -40,6 +32,8 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { allowedExtensions: allowedExtensions, onFileLoading: onFileLoading, compressionQuality: compressionQuality, + windowsOptions: WindowsOptions(lockParentWindow: lockParentWindow), + linuxOptions: LinuxOptions(lockParentWindow: lockParentWindow), ); return await result?.toAttachment(type: type.toAttachmentType()); diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart index 9ff9d8b3f7..b666d4ff44 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart @@ -134,14 +134,6 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { bool withReadStream = false, bool lockParentWindow = true, }) async { - // pickFile (singular) since file_picker 12: it returns the `PlatformFile?` - // this method already wanted, instead of a result wrapper we immediately - // took `.files.first` from — which threw on an empty selection. - // - // `withData` / `withReadStream` are no longer forwarded: file_picker 12 - // deprecated them in favour of reading through PlatformFile.readAsBytes() - // / readAsByteStream(), which is what toAttachmentFile now does. They stay - // in this method's signature so this is not a breaking change for callers. final result = await FilePicker.pickFile( dialogTitle: dialogTitle, initialDirectory: initialDirectory, @@ -149,6 +141,8 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { allowedExtensions: allowedExtensions, onFileLoading: onFileLoading, compressionQuality: compressionQuality, + windowsOptions: WindowsOptions(lockParentWindow: lockParentWindow), + linuxOptions: LinuxOptions(lockParentWindow: lockParentWindow), ); return await result?.toAttachment(type: type.toAttachmentType()); diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index 0661e812f0..5e3271eb2a 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -116,9 +116,7 @@ extension IterableExtension on Iterable { extension PlatformFileX on PlatformFile { /// Converts the [PlatformFile] into [AttachmentFile] /// - /// Asynchronous since file_picker 12: `PlatformFile` no longer exposes the - /// eagerly-loaded `bytes`/`size` getters, the content is read on demand. - /// Mirrors [XFileX.toAttachmentFile] below. + /// Reads the file content on demand, so the result is asynchronous. Future get toAttachmentFile async { final bytes = await readAsBytes(); return AttachmentFile( From 2e17a37294721a2f2b92ce7e7e2e85b9bea8f13e Mon Sep 17 00:00:00 2001 From: Yasmany Cubela Medina Date: Wed, 26 Aug 2026 15:02:40 +0000 Subject: [PATCH 4/4] fix(sample): evaluate plugin projects after :app file_picker 12 pulls in android_file_picker, whose build script reads the `flutter` Gradle extension at configuration time. The Flutter Gradle plugin only registers that extension on plugin projects while `:app` is being configured, and plugin projects evaluate first by default, so the Android build failed with "Extension with name 'flutter' does not exist". The current Flutter app template carries this same block; sample_app predates it. It goes after the existing subprojects block rather than inside it: forcing evaluation from there runs `:app` before its own afterEvaluate is registered, which Gradle rejects outright. --- sample_app/android/build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sample_app/android/build.gradle b/sample_app/android/build.gradle index 22f412dfaa..79709112b1 100644 --- a/sample_app/android/build.gradle +++ b/sample_app/android/build.gradle @@ -25,6 +25,17 @@ subprojects { } +// Plugin projects evaluate before `:app` by default, but the Flutter Gradle +// plugin only registers the `flutter` extension on them while `:app` is being +// configured. A plugin whose build script reads that extension at configuration +// time — android_file_picker, pulled in by file_picker 12 — fails without this. +// Present in the current Flutter app template; this module predates it. Kept in +// its own block, after the one above: forcing evaluation from inside that block +// would run `:app` before its `afterEvaluate` is registered. +subprojects { + project.evaluationDependsOn(":app") +} + tasks.register("clean", Delete) { delete rootProject.layout.buildDirectory } \ No newline at end of file