React Native Image Not Displaying on Android Only
Images that render correctly on iOS but show blank, broken, or invisible specifically on Android usually come down to one of a few Android-specific behaviors around image loading, URI formatting, or explicit sizing requirements that iOS is more forgiving about by default.
The Problem
An Image component displays the expected picture on iOS, but on Android the same component shows nothing — no error, no placeholder, just empty space where the image should be.
Why It Happens
Android's image loading and layout behavior differs from iOS in several specific ways that commonly trip up cross-platform code:
- The
Imagecomponent has no explicitwidthandheight(or astyledefining them), and Android is stricter than iOS about needing explicit dimensions to actually render a remote image — without them, Android may collapse the component to zero size - The image URI uses
http://instead ofhttps://, and Android blocks cleartext (non-HTTPS) network requests by default starting from a certain API level, silently failing to load the image with no visible error in the UI itself - A local image is referenced with a file path format that works on iOS's filesystem conventions but not Android's, particularly common when dealing with images picked from the device's camera roll or downloaded to local storage
- Missing network or storage permissions in the Android manifest, if using a bare/prebuild workflow where these need to be explicitly declared rather than assumed
The Fix
First, confirm the Image component has explicit dimensions defined, since this is the single most common cause of an Android-only blank image:
<Image
source={{ uri: imageUrl }}
style={{ width: 200, height: 200 }}
/>
Without a defined width and height, Android may render the image container at zero size even though the image itself loaded successfully, which looks identical to a failed load but has a completely different cause.
If the image URL uses plain HTTP, either switch the source to HTTPS (the correct long-term fix), or, if you specifically need to allow HTTP for development purposes, explicitly permit cleartext traffic in the Android manifest:
<application android:usesCleartextTraffic="true">
For a more targeted approach limited to specific domains rather than the whole app, use a network security config file instead of the blanket manifest flag — this keeps HTTPS enforcement everywhere else while allowing HTTP only where genuinely needed.
For local images (picked from the camera roll, downloaded, or cached), confirm the URI format matches what Android expects. A path returned by an image picker library often needs a file:// prefix that isn't always automatically included, depending on the specific library and version:
const uri = Platform.OS === 'android' && !path.startsWith('file://')
? `file://${path}`
: path;
Confirm your Android manifest includes any permissions the image loading actually requires, particularly if reading from external storage rather than the app's own sandboxed directory:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Note that newer Android versions have moved toward scoped storage, which changes what direct file path access actually requires — if you're targeting a recent Android SDK version, using the platform's proper media/document picker APIs (rather than raw file paths) is generally more reliable than manually managing permissions for direct filesystem access.
Still Not Working?
If dimensions, protocol, and permissions all check out, add the onError callback to the Image component to capture the actual underlying error Android is encountering, rather than only seeing a silent blank space:
<Image
source={{ uri: imageUrl }}
style={{ width: 200, height: 200 }}
onError={(e) => console.log('Image failed to load:', e.nativeEvent.error)}
/>
This surfaces platform-specific error messages (like a malformed URI or a network failure) that are otherwise completely invisible in the UI, turning a vague "it just doesn't show" into a concrete, actionable error message.