Skip to content

feat(platform_interface): add PlatformFile.size - #2191

Merged
vicajilau merged 16 commits into
mainfrom
feat/platform-file-size
Sep 2, 2026
Merged

vicajilau merged 16 commits into
mainfrom
feat/platform-file-size

Conversation

@vicajilau

Copy link
Copy Markdown
Owner

Fixes #2187.

The request

PlatformFile.size was removed in the v12 federated rewrite without a synchronous replacement, only Future<int> length() remains. That forces an await for a value most callers already have on hand.

The fix

Every platform's PlatformFile already carries the size the native picker reports, stored in a private _bytesLength field. length() already returns it immediately when it's known and only falls back to actually reading the file (xFile.length()) when it isn't, so the async signature exists for that fallback path, not because getting the size is inherently async.

Added int? get size to PlatformFile, returning that already known value synchronously, null when it isn't known. Matches the shape extension and path already use on this class.

Per platform, what size actually returns:

Platform Source Known upfront?
Android native pick result (FileInfo.size) Yes
iOS / macOS native pick result Yes
Web browser File.size, or loaded bytes Yes
Windows picker only returns a path No, null
Linux XDG portal only returns a path No, null

Windows and Linux callers fall back to await length(), which still works exactly as before.

What changed

  • file_picker_platform_interface 3.2.0 → 3.3.0: added the abstract member.
  • file_picker_darwin, android_file_picker, file_picker_web bumped (minor): implemented size backed by data they already have.
  • file_picker_linux, windows_file_picker bumped (minor): implemented size, returns null since neither picker reports it.
  • Each implementation's file_picker_platform_interface lower bound raised to ^3.3.0, the version that introduces the abstract member they now implement. Learned this the hard way with You forgot to bump platform implementations version! #2186, an implementation built against an older interface won't have this override and will fail to compile if pub resolves it alongside a newer interface.
  • file_picker (the facade) is untouched, PlatformFile is used through the platform interface as-is, no facade code references size directly.

Test plan

  • flutter analyze clean across the whole workspace
  • dart format --output=none --set-exit-if-changed clean
  • flutter test across the whole workspace, 57 tests pass, including new coverage for size on every platform (web's is @TestOn('browser') like the rest of that file, doesn't run under the default flutter test)

Restores a synchronous way to get a picked file's size, addressing #2187.
Every platform implementation already carries the size the native picker
reports when it reports one, length() only falls back to actually reading
the file when it doesn't. size exposes that already-known value directly
instead of wrapping it in a Future for a case that mostly doesn't apply.

Returns null when the size isn't known upfront (Windows and Linux, whose
pickers only return a path), callers needing a guaranteed value use
length() as before.
@vicajilau vicajilau mentioned this pull request Sep 1, 2026
5 tasks
Comment thread packages/file_picker/test/file_picker_test.dart Outdated
Comment thread packages/file_picker_android/CHANGELOG.md Outdated
Comment thread packages/file_picker_linux/CHANGELOG.md Outdated
Comment thread packages/file_picker_windows/CHANGELOG.md
…ze override

- Accepted the suggested shorter wording for the android_file_picker
  changelog entry.
- Added a dartdoc line on each platform's `size` override describing what
  it actually returns there (cached native value, browser File.size, or
  null with a pointer to length() on Linux/Windows), instead of leaving
  that only in the changelog.
Comment thread packages/file_picker_linux/lib/src/linux_platform_file.dart Outdated
Comment thread packages/file_picker_windows/lib/src/windows_platform_file.dart Outdated
Comment thread packages/file_picker_platform_interface/CHANGELOG.md Outdated
vicajilau and others added 6 commits September 1, 2026 21:28
Co-authored-by: Navaron Bracke <brackenavaron@gmail.com>
All six conflicts were the same shape and none of them semantic. This
branch bumps each package's version, master rewrote the homepage and
repository URLs after the repo moved from miguelpruivo to vicajilau, and
the two edits land on adjacent lines.

Resolved by keeping both: this branch's version, master's URLs.

Checked that the branch introduces no miguelpruivo URLs of its own on
lines git merged cleanly, which a conflict marker would never have
flagged. It does not. The historical links left in the CHANGELOGs are
untouched, matching what the transfer commit itself did.
PlatformFile.size is abstract, so an implementation published before it
does not satisfy the interface that declares it. The facade still asked
for platform_interface ^3.2.0 alongside implementations at ^1.0.3 and
friends, so once this releases pub could pair interface 3.3.0 with an
implementation that has no size and produce a resolution that compiles
nowhere. That is issue #2186 again, same shape, different member.

Raises all six bounds in lockstep with the versions this branch
publishes, and takes the facade to 12.2.0 since size is new API reaching
its users.

The downgrade smoke test in CI cannot see this one. It resolves every
dependency to its lowest allowed version, and when the interface bound
and the implementation bounds are stale together that minimum set is
internally consistent and builds fine, while a real app is still free to
pair the new interface with an old implementation. It did catch #2186
only because the interface bound had already been raised there.

Adds a test asserting the property the build cannot see: the facade must
require at least the versions the workspace is about to publish. It
reads the pubspecs off disk, needs no network, and runs in the existing
tooling test step. Verified that it reports all four stale bounds this
branch arrived with.
Two conflicts, both from #2193 landing on file_picker_android. The
version goes to 1.1.0, which supersedes master's 1.0.4, and both
CHANGELOG sections stay: they are two different releases.

Also makes the downgrade smoke test wait for the release instead of
failing on it. A branch that raises the facade's bounds points at
versions pub.dev does not have yet, so resolution failed with "every
version of file_picker from path depends on windows_file_picker ^1.2.0
which doesn't match any versions", which says nothing about whether the
constraints are right.

Overriding those to the workspace was the obvious fix and does not work:
every package here declares `resolution: workspace`, and pub then drops
file_picker itself from the resolution. Verified locally, the build
fails with "Couldn't resolve the package 'file_picker'".

So the step now detects that case and skips with a notice naming the
versions it is waiting on. The invariant it can no longer check on
release branches is the one tool/test/facade_constraints_test.dart
covers, without needing pub.dev.
Addresses review feedback that the line read oddly. It led with a bare
'null' and packed the reason into a trailing clause. Now it states when
the value is known first, then why a picked file has none.
Review feedback: `size` and `length()` named the same thing two
different ways. `length()` / `lengthSync()` is the pair `dart:io`'s File
already uses, and it is what the issue asked for in the first place.

Turns the getter into a method to match, across the interface and all
five implementations, their tests and the changelogs.

Left the browser's own `File.size` alone in file_picker_web, which is the
JS API and not this one.
Same shape as before: this branch bumps each version, main rewrote the
tree/master URLs after the default branch rename, and the two land on
adjacent lines. Kept this branch's versions and main's URLs.

@navaronbracke navaronbracke left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, although I'm beginning to think we should maybe rethink the facade version checks.

Usually what happens is the platform interface package gets published first, on it's own, then a subsequent release of the child packages can depend on the new platform interface.

Is that a solution to the issue?

@vicajilau

vicajilau commented Sep 2, 2026 •

Copy link
Copy Markdown
Owner Author

Thanks! And yes, it does solve the CI part, for a reason worth spelling out: with a staged release the facade never references a version that is not on pub.dev yet. That is the only thing the downgrade smoke test was choking on, so the "skip when unpublished" branch I added to it stops being necessary.

There is a catch though, and it is mine, not yours. It clashes with the guard I put in this PR. tool/test/facade_constraints_test.dart asserts that the facade requires at least the versions the workspace is about to publish, which is lockstep by construction. I simulated your stage one, interface bumped alone with the facade untouched, and it fails on all six:

file_picker_platform_interface: facade allows >=3.2.0 but this repo publishes 3.3.0
android_file_picker: facade allows >=1.0.3 but this repo publishes 1.1.0
file_picker_darwin: facade allows >=1.0.4 but this repo publishes 1.1.0
file_picker_linux: facade allows >=1.0.2 but this repo publishes 1.1.0
windows_file_picker: facade allows >=1.1.0 but this repo publishes 1.2.0
file_picker_web: facade allows >=3.0.3 but this repo publishes 3.1.0

So one of the two has to go. I would keep yours and change what the guard compares against: not "the versions the workspace is about to publish" but "the first published version of each implementation that was built against the interface the facade requires". That works at every stage of a staged release, and it still catches #2186, where the facade asked for interface ^3.2.0 alongside implementations built against ^3.0.0.

One thing staged releases do not remove, just so it is on the record: between publishing the interface and publishing the facade, the new interface is live while the old facade still allows implementations that predate it, so the broken pair is resolvable in that window. It exists in the all-at-once flow too, only shorter. The only thing that actually closes it is not adding abstract members in a minor, giving lengthSync() a default of null instead. That buys safety at the cost of losing the compile error that tells us an implementation forgot to implement it, which for first party implementations is worth a lot.

Doing the guard swap here rather than in a follow-up, so the change and the check that backs it land together.

The previous guard asserted that the facade requires at least the
versions the workspace is about to publish. That is lockstep by
construction, and it rules out the staged release flow navaronbracke
described in review, where the platform interface is published on its
own and the implementations catch up in a later release. Simulating that
first stage failed the old guard on all six packages.

The property checked now holds under either flow: for every
implementation, the facade's lower bound must be at least the first
published version of that implementation built against the interface the
facade requires. It reads that from pub.dev, which is also the pairing
the downgrade build in CI can never produce, since resolving everything
to its lowest version never puts the newest interface next to the oldest
implementation.

Verified in three directions: it reports all four stale bounds against
the pubspec that caused #2186, it passes on the staged flow's first
stage, and on a branch like this one, where the interface version the
facade asks for is not published yet, it skips and says so rather than
inventing an answer. The arithmetic and parsing have seven offline tests
of their own, including a fixture that reproduces #2186.
@navaronbracke

Copy link
Copy Markdown
Collaborator

A follow up is fine, thanks!

@vicajilau
vicajilau enabled auto-merge (squash) September 2, 2026 17:02
@vicajilau
vicajilau disabled auto-merge September 2, 2026 17:03
@vicajilau
vicajilau merged commit a036d5e into main Sep 2, 2026
16 of 17 checks passed
@vicajilau
vicajilau deleted the feat/platform-file-size branch September 2, 2026 17:03
navaronbracke pushed a commit to navaronbracke/flutter_file_picker that referenced this pull request Sep 15, 2026
file_picker_windows is the Windows implementation, so a '#### Desktop
(Windows)' heading above every entry says nothing. It was the only
implementation package still carrying one, twice.

The facade keeps its per-platform headings, where they do the work of
telling apart entries that cover different platforms.

Text only, on versions already published, so no version bump. It shows
up on pub.dev with the next release.

Split out of vicajilau#2191 as agreed there.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PlatformFile.size property

2 participants