Understanding React Native File URIs Before You Try to Upload One
TIL that a file:// URI from Expo's camera isn't a file you can send as-is — it's a path into a cache directory the OS can clear, and the upload code needs to treat it that way.
The Problem #
expo-camera and expo-image-picker hand back a file:// URI pointing at the captured photo, and my first instinct was to treat that string like any other value — store it, pass it around, upload it whenever convenient. Uploads that happened right after capture worked fine; uploads that happened a bit later sometimes failed with the file just... not being there.
Context #
The photo-proof-of-delivery flow captured a photo, then queued the upload behind a few other API calls (marking the delivery complete, updating status) rather than uploading immediately.
What I Tried #
Assumed the failure was a network issue and added retry logic around the upload request.
What Went Wrong #
Retries didn't help, because the problem wasn't the network — the file itself was gone. file:// URIs from the camera/picker point into app-specific cache directories that the OS is explicitly allowed to clear under storage pressure, and there's no guarantee that path is still valid by the time a queued upload gets around to reading it.
The Solution #
Stopped treating the URI as a durable reference. Either upload immediately while the file is guaranteed to still exist, or copy it into the app's own persistent document directory first if the upload genuinely needs to be deferred.
import * as FileSystem from "expo-file-system";
const persistentUri = `${FileSystem.documentDirectory}${filename}`;
await FileSystem.copyAsync({ from: cameraUri, to: persistentUri });
// Safe to reference persistentUri later; cameraUri is not guaranteed to be.Why It Works #
documentDirectory is meant for data the app owns and controls the lifetime of; cache directories are explicitly not. Copying the file the moment you capture it converts an ephemeral, OS-managed reference into one your app fully controls.
Lessons Learned #
A file:// string isn't a stable handle to a file — it's a path, and paths into cache directories can stop resolving to anything at all. Anything you plan to use later than "immediately" needs to live somewhere the OS has promised not to touch.
What I Would Do Differently #
I'd treat every camera/picker result as ephemeral by default and copy anything that isn't uploaded within the same tick, instead of learning the cache-eviction behavior from an intermittent, hard-to-reproduce bug report.
Related Concepts #
iOS/Android app sandbox storage, cache vs. document directories, expo-file-system.
Related content
Building React Native Apps with Expo: What the Managed Workflow Actually Buys You
TIL that starting a React Native app in Expo's managed workflow paid off immediately for camera/location access, but I still had to understand what 'ejecting' would cost before I needed it.
Designing a Delivery App for Unreliable Networks, Not Just Offline
TIL that 'handle offline' undersold the actual problem — the failure mode that hurt drivers most was a flaky, half-connected state, not a clean offline/online boundary.
Android-Specific React Native Problems That Never Show Up on iOS
TIL that a React Native feature working perfectly in the iOS simulator is not evidence it'll work on Android — cleartext traffic, back-button handling, and permission dialogs all diverge silently.