Uh oh!
There was an error while loading. Please reload this page.
fix(path_provider_tvos): surface the tvOS sandbox write restrictions - #6
Conversation
The plugin was written on the assumption that tvOS has a normal iOS-style sandbox, and its header comment said so: "tvOS has a normal app sandbox (Documents, Library, Library/Caches, Library/Application Support) ... so the standard NSSearchPath* lookups work unchanged here." That is wrong for two of the four directories it names. Measured on a physical Apple TV 4K (tvOS 26.5) by writing a file into each: tmp writable Library/Caches writable Documents exists, writes DENIED (errno 1) Library/Application Support does not exist, CANNOT be created (errno 1) The tvOS simulator permits all of these writes, which is why it went unnoticed — the same asymmetry that hid the sqflite_tvos default-path bug (#4). The concrete defect: getApplicationSupportDirectory() ran createDirectory through `try?`, so a genuinely failed creation was swallowed and the caller received a path that does not exist and cannot be made. The failure only surfaced at the caller's first write, as an errno naming nothing useful. It now returns nil, which path_provider surfaces as MissingPlatformDirectoryException at the call site. getApplicationDocumentsDirectory() keeps returning its path: the directory is real and reads work, so reporting it is not a lie — but the README now says plainly that writes fail there and points callers at getApplicationCacheDirectory(). Verified on the device through the public path_provider API after the change: temporary -> writable appCache -> writable appDocuments -> returned, PathAccessException on write appSupport -> MissingPlatformDirectoryException library -> returned, PathAccessException on write downloads -> null
MAUstaoglu
commented
Jul 20, 2026
Reproduced the broken behaviour on hardware, so the before-state is now observed rather than inferred from reading Same physical Apple TV 4K (tvOS 26.5), same probe, plugin reverted to
Before / after through the public
The failure now happens where the problem is, with a message that says what's wrong. |
DenisovAV
left a comment
There was a problem hiding this comment.
Reviewed the Swift change + the docs. This is a correct, honest fix — the getApplicationSupportDirectory path is right (fail loud with MissingPlatformDirectoryException instead of handing back a path that can't be created), the do/try/catch replacing try? is the fix, the breaking change is flagged, and the on-device measurement is documented properly. No blockers.
One design note on getApplicationDocumentsDirectory (inline), and a minor: no test covers the new nil-return path. On-device dir behavior can't be unit-tested (the simulator lies, as the PR notes), but a Dart mock-method-channel test could at least assert App Support now surfaces the exception. Optional.
LGTM once you've decided on the Documents point.
| case "getTemporaryDirectory": | ||
| result(NSTemporaryDirectory()) | ||
| case "getApplicationDocumentsDirectory": | ||
| // Returned for parity with iOS, but NOT writable on tvOS — see above. |
There was a problem hiding this comment.
Worth deciding explicitly — the PR title says "stop returning directories tvOS cannot write", but Documents (writes denied per your own table) is still returned here. So the most common path_provider usage — getApplicationDocumentsDirectory() + write a file — still fails silently at the first write with the same unhelpful errno this PR fixes for App Support. It's only mitigated by docs ("use getApplicationCacheDirectory instead").
Keeping the path is defensible: Documents exists and reads work (some apps read pre-populated data), nil'ing it would break iOS parity and readers, and writability can depend on entitlements — so returning it lets apps that can write still work, whereas App Support genuinely can't be created. That asymmetry is reasonable.
But the silent-write-failure the PR sets out to kill still lives for the more common directory. Cheap way to make it loud without breaking parity or readers: a one-time NSLog when getApplicationDocumentsDirectory is called on tvOS ("Documents is not writable on a real Apple TV; use the cache directory"). Either add that, or soften the title to reflect that Documents is a docs-only mitigation. Your call — not a blocker.
Review point: the branch stops returning Application Support, which cannot be created, but still returns Documents, which cannot be written. So the silent write failure this change set out to kill survived for the *more common* call — getApplicationDocumentsDirectory() + write a file — mitigated only by documentation. Keep returning the path: Documents exists, reads work, some apps ship pre-populated data there, and returning nil would break iOS parity and readers. That asymmetry with Application Support is deliberate. But make the failure traceable — log once, on first request, naming the sandbox restriction and pointing at getApplicationCacheDirectory(). A `static let` is lazily initialised exactly once per process, so this is a thread-safe one-shot with no flag to manage. Verified on a physical Apple TV 4K (tvOS 26.5). Calling getApplicationDocumentsDirectory() twice, then writing: PP_DOC_WRITE FAILED PathAccessException PP_SUPPORT THREW MissingPlatformDirectoryException with exactly one warning in the device log for the two calls, plus the Application Support failure now naming the real cause instead of being swallowed by `try?`: [path_provider_tvos] cannot create .../Library/Application Support: You don't have permission to save the file "Application Support" in the folder "Library".. The tvOS sandbox only permits writes to Library/Caches and tmp. Note the CLI's simulator log stream filters on the Flutter image, so a plugin NSLog does not surface in `flutter-tvos run` there; on device it comes through the console. A simulator run also creates Application Support without complaint — the divergence from hardware that hid this.
The package had one test asserting the class exists. Replace it with tests that would actually fail if this branch regressed. The device behaviour itself cannot be unit-tested — the simulator happily creates Application Support, which is what hid the bug — so these lock in the Dart half of the contract the Swift change depends on: given nil from the native side, the app-facing call must raise MissingPlatformDirectoryException rather than hand back a path. Also covered: every getter maps to the right channel method, downloads short-circuits without a channel round-trip, the Android-only APIs still throw, and Documents keeps returning its path (nil'ing it would break iOS parity and readers). Adds path_provider as a dev_dependency: it is the layer that converts a null platform result into the exception, so asserting the user-visible behaviour needs it. Checked the tests have teeth by making getApplicationSupportPath fall back to a path instead of returning null — the Application Support test fails, the rest stay green.
MAUstaoglu
commented
Jul 21, 2026
Both points addressed, and you were right that the title overclaimed. Documents: warn rather than soften the titleTook the first option. Keeping the path is the call for the reasons you laid out — Documents exists, reads work, apps ship pre-populated data there, and nil'ing it breaks iOS parity — but you're right that the silent write failure surviving for the more common call undercut the whole point of the change. There's now a one-time
Retitled anyway: even with the warning, "stop returning directories tvOS cannot write" isn't accurate while Documents is still returned. Verified on the physical Apple TV 4K (tvOS 26.5) — two calls, one warning: A side effect of dropping One trap worth recording: the CLI's simulator log stream filters on the Flutter image, so a plugin TestsAdded. The package had a single test asserting the class exists; it now has six. To be straight about what they do and don't prove: they can't test the Swift, and the simulator actively lies here — it creates Application Support without complaint, which is exactly what hid this. What they lock in is the Dart half of the contract the Swift change depends on: given nil from native, the app-facing call must raise Checked they have teeth rather than trusting a green run — made
|
Follow-up to #4. That PR fixed sqflite's default database path; this fixes the package that told everyone to put it there.
The wrong assumption
PathProviderPlugin.swiftwas written believing tvOS has an iOS-style sandbox, and said so in its header:That is wrong for two of the four directories it names. Measured on a physical Apple TV 4K (tvOS 26.5) by writing a file into each:
tmpLibrary/CachesDocumentsLibrary/Application SupportThe tvOS simulator permits all of these writes — the same asymmetry that hid the sqflite default-path bug in #4, and the reason this survived the "verified on a physical Apple TV" note in the README.
The concrete defect
getApplicationSupportDirectory()rancreateDirectorythroughtry?. On tvOS that creation genuinely fails, the error was swallowed, and the caller got back a path that does not exist and cannot be made — discovering it only at their first write, as an errno naming nothing useful.It now returns
nil, whichpath_providersurfaces asMissingPlatformDirectoryExceptionat the call site, and logs the reason.getApplicationDocumentsDirectory()deliberately keeps returning its path. The directory is real and reads work, so reporting it isn't a lie — and returningnilwould break cross-platform code that calls it unconditionally. Instead the README now states plainly that writes fail there and points callers atgetApplicationCacheDirectory().Verified on hardware, after the change
Through the public
path_providerAPI on the physical Apple TV:Matches the documented table exactly, including
getLibraryDirectory()being a container you can't write to directly.Behaviour change
Apps calling
getApplicationSupportDirectory()on tvOS now get an exception instead of an unusable path. That's the point — it fails where the problem is. Documented in the CHANGELOG with the migration (getApplicationCacheDirectory()), plus the reminder that tvOS storage is purgeable by platform contract, so durable data belongs on a server or in iCloud key-value storage.Version
0.0.2→0.0.3.