From ca46a076be4b22abf307574429b773919b94649d Mon Sep 17 00:00:00 2001 From: Devansh Saini Date: Tue, 3 Feb 2026 18:01:01 +0530 Subject: [PATCH 1/3] fix: local svg not working in release builds --- .../dylanvann/fastimage/FastImageSource.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java b/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java index 86ee08e6..8fc1566b 100644 --- a/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +++ b/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java @@ -55,11 +55,31 @@ public FastImageSource(Context context, String source, double width, double heig mHeaders = headers == null ? Headers.DEFAULT : headers; mUri = imageSource.getUri(); - if (isResource() && TextUtils.isEmpty(mUri.toString())) { + boolean isUriEmpty = mUri == null || TextUtils.isEmpty(mUri.toString()); + + // If URI is empty (drawable lookup failed), try to find in res/raw folder + if (isUriEmpty && source != null && !source.startsWith("http") && !source.startsWith("data:")) { + String rawResourceName = source.toLowerCase().replace("-", ""); + int rawResId = context.getResources().getIdentifier( + rawResourceName, + "raw", + context.getPackageName() + ); + + if (rawResId != 0) { + mUri = Uri.parse(ANDROID_RESOURCE_SCHEME + "://" + + context.getPackageName() + "/raw/" + rawResourceName); + } + } + + // Re-check after raw folder lookup + isUriEmpty = mUri == null || TextUtils.isEmpty(mUri.toString()); + + if (isResource() && isUriEmpty) { throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'."); } - if (isLocalResourceUri(mUri)) { + if (mUri != null && isLocalResourceUri(mUri)) { // Convert res:/ scheme to android.resource:// so // glide can understand the uri. mUri = Uri.parse(mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/")); From 2c532685b1f4fa2eef1173f8ff8f15e6a64bac1b Mon Sep 17 00:00:00 2001 From: Devansh Saini Date: Tue, 3 Feb 2026 18:05:06 +0530 Subject: [PATCH 2/3] fix: same name causes build failure --- .../src/LocalImagesExample.tsx | 4 ++-- .../src/images/{fields.webp => fields_alt.webp} | Bin .../images/{jellyfish.webp => jellyfish_anim.webp} | Bin 3 files changed, 2 insertions(+), 2 deletions(-) rename ReactNativeFastImageExample/src/images/{fields.webp => fields_alt.webp} (100%) rename ReactNativeFastImageExample/src/images/{jellyfish.webp => jellyfish_anim.webp} (100%) diff --git a/ReactNativeFastImageExample/src/LocalImagesExample.tsx b/ReactNativeFastImageExample/src/LocalImagesExample.tsx index c6beae94..b476d2b7 100644 --- a/ReactNativeFastImageExample/src/LocalImagesExample.tsx +++ b/ReactNativeFastImageExample/src/LocalImagesExample.tsx @@ -16,11 +16,11 @@ import BulletText from './BulletText'; // @ts-ignore import FieldsImage from './images/fields.jpg'; // @ts-ignore -import FieldsWebP from './images/fields.webp'; +import FieldsWebP from './images/fields_alt.webp'; // @ts-ignore import JellyfishGIF from './images/jellyfish.gif'; // @ts-ignore -import JellyfishWebP from './images/jellyfish.webp'; +import JellyfishWebP from './images/jellyfish_anim.webp'; // @ts-ignore import ReactSvg from './images/react-native.svg'; diff --git a/ReactNativeFastImageExample/src/images/fields.webp b/ReactNativeFastImageExample/src/images/fields_alt.webp similarity index 100% rename from ReactNativeFastImageExample/src/images/fields.webp rename to ReactNativeFastImageExample/src/images/fields_alt.webp diff --git a/ReactNativeFastImageExample/src/images/jellyfish.webp b/ReactNativeFastImageExample/src/images/jellyfish_anim.webp similarity index 100% rename from ReactNativeFastImageExample/src/images/jellyfish.webp rename to ReactNativeFastImageExample/src/images/jellyfish_anim.webp From bdaab19038dd36e81e7d0396ebcce80619fdd0b0 Mon Sep 17 00:00:00 2001 From: Devansh Saini Date: Tue, 3 Feb 2026 18:28:21 +0530 Subject: [PATCH 3/3] refactor: FastImageSource --- .../dylanvann/fastimage/FastImageSource.java | 106 ++++++++++++++---- 1 file changed, 83 insertions(+), 23 deletions(-) diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java b/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java index 8fc1566b..4ab49234 100644 --- a/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +++ b/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java @@ -17,6 +17,10 @@ public class FastImageSource { private static final String ANDROID_RESOURCE_SCHEME = "android.resource"; private static final String ANDROID_CONTENT_SCHEME = "content"; private static final String LOCAL_FILE_SCHEME = "file"; + private static final String RAW_RESOURCE_FOLDER = "raw"; + private static final String HTTP_SCHEME_PREFIX = "http"; + private static final String DATA_URI_PREFIX = "data:"; + private final Headers mHeaders; private Uri mUri; private String mSource; @@ -53,39 +57,95 @@ public FastImageSource(Context context, String source, double width, double heig ImageSource imageSource = new ImageSource(context, source, width, height); mSource = imageSource.getSource(); mHeaders = headers == null ? Headers.DEFAULT : headers; - mUri = imageSource.getUri(); + mUri = resolveResourceUri(context, source, imageSource.getUri()); boolean isUriEmpty = mUri == null || TextUtils.isEmpty(mUri.toString()); + boolean isResourceMissing = isResource() && isUriEmpty; - // If URI is empty (drawable lookup failed), try to find in res/raw folder - if (isUriEmpty && source != null && !source.startsWith("http") && !source.startsWith("data:")) { - String rawResourceName = source.toLowerCase().replace("-", ""); - int rawResId = context.getResources().getIdentifier( - rawResourceName, - "raw", - context.getPackageName() - ); - - if (rawResId != 0) { - mUri = Uri.parse(ANDROID_RESOURCE_SCHEME + "://" + - context.getPackageName() + "/raw/" + rawResourceName); - } + if (isResourceMissing) { + throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'."); } - // Re-check after raw folder lookup - isUriEmpty = mUri == null || TextUtils.isEmpty(mUri.toString()); - - if (isResource() && isUriEmpty) { - throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'."); + boolean shouldConvertLocalResourceScheme = mUri != null && isLocalResourceUri(mUri); + + if (shouldConvertLocalResourceScheme) { + // Convert res:/ scheme to android.resource:// so glide can understand the uri. + String convertedUri = mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/"); + mUri = Uri.parse(convertedUri); } + } - if (mUri != null && isLocalResourceUri(mUri)) { - // Convert res:/ scheme to android.resource:// so - // glide can understand the uri. - mUri = Uri.parse(mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/")); + /** + * Attempts to find a resource URI for the given source. + * First uses the URI from ImageSource (checks drawable folder), + * then falls back to raw folder for assets like SVGs in release builds. + * + * @param context Application context + * @param source Original source string + * @param initialUri URI resolved by ImageSource + * @return Resolved URI or null if not found + */ + @Nullable + private Uri resolveResourceUri(Context context, String source, Uri initialUri) { + boolean isInitialUriValid = initialUri != null && !TextUtils.isEmpty(initialUri.toString()); + + if (isInitialUriValid) { + return initialUri; } + + boolean isSourceNull = source == null; + boolean isRemoteUrl = source != null && source.startsWith(HTTP_SCHEME_PREFIX); + boolean isDataUri = source != null && source.startsWith(DATA_URI_PREFIX); + boolean shouldSkipRawLookup = isSourceNull || isRemoteUrl || isDataUri; + + if (shouldSkipRawLookup) { + return initialUri; + } + + return tryResolveFromRawFolder(context, source); + } + + /** + * Attempts to find a resource in the res/raw folder. + * This is needed because SVG and some other assets are bundled to res/raw/ + * in Android release builds, but ImageSource only checks res/drawable/. + * + * @param context Application context + * @param source Original source string + * @return URI pointing to raw resource, or null if not found + */ + @Nullable + private Uri tryResolveFromRawFolder(Context context, String source) { + String rawResourceName = toAndroidResourceName(source); + int rawResId = context.getResources().getIdentifier( + rawResourceName, + RAW_RESOURCE_FOLDER, + context.getPackageName() + ); + + boolean resourceNotFound = rawResId == 0; + + if (resourceNotFound) { + return null; + } + + String rawResourceUri = ANDROID_RESOURCE_SCHEME + "://" + + context.getPackageName() + "/" + RAW_RESOURCE_FOLDER + "/" + rawResourceName; + + return Uri.parse(rawResourceUri); } + /** + * Converts a source name to Android resource name format. + * Android resources must be lowercase and cannot contain hyphens. + * Hyphens are removed to match React Native's bundling behavior. + * + * @param source Original source name + * @return Android-compatible resource name + */ + private String toAndroidResourceName(String source) { + return source.toLowerCase().replace("-", ""); + } public boolean isBase64Resource() { return mUri != null && FastImageSource.isBase64Uri(mUri);