React Native

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 Image component has no explicit width and height (or a style defining 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 of https://, 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.