Skip to content

Commit eabb78f

Browse files
fix: local svg not working in release builds (#370)
* fix: local svg not working in release builds * fix: same name causes build failure * refactor: FastImageSource
1 parent 2ae7a1b commit eabb78f

4 files changed

Lines changed: 88 additions & 8 deletions

File tree

ReactNativeFastImageExample/src/LocalImagesExample.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import BulletText from './BulletText';
1616
// @ts-ignore
1717
import FieldsImage from './images/fields.jpg';
1818
// @ts-ignore
19-
import FieldsWebP from './images/fields.webp';
19+
import FieldsWebP from './images/fields_alt.webp';
2020
// @ts-ignore
2121
import JellyfishGIF from './images/jellyfish.gif';
2222
// @ts-ignore
23-
import JellyfishWebP from './images/jellyfish.webp';
23+
import JellyfishWebP from './images/jellyfish_anim.webp';
2424
// @ts-ignore
2525
import ReactSvg from './images/react-native.svg';
2626

File renamed without changes.

ReactNativeFastImageExample/src/images/jellyfish.webp renamed to ReactNativeFastImageExample/src/images/jellyfish_anim.webp

File renamed without changes.

android/src/main/java/com/dylanvann/fastimage/FastImageSource.java

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ public class FastImageSource {
1717
private static final String ANDROID_RESOURCE_SCHEME = "android.resource";
1818
private static final String ANDROID_CONTENT_SCHEME = "content";
1919
private static final String LOCAL_FILE_SCHEME = "file";
20+
private static final String RAW_RESOURCE_FOLDER = "raw";
21+
private static final String HTTP_SCHEME_PREFIX = "http";
22+
private static final String DATA_URI_PREFIX = "data:";
23+
2024
private final Headers mHeaders;
2125
private Uri mUri;
2226
private String mSource;
@@ -53,19 +57,95 @@ public FastImageSource(Context context, String source, double width, double heig
5357
ImageSource imageSource = new ImageSource(context, source, width, height);
5458
mSource = imageSource.getSource();
5559
mHeaders = headers == null ? Headers.DEFAULT : headers;
56-
mUri = imageSource.getUri();
60+
mUri = resolveResourceUri(context, source, imageSource.getUri());
61+
62+
boolean isUriEmpty = mUri == null || TextUtils.isEmpty(mUri.toString());
63+
boolean isResourceMissing = isResource() && isUriEmpty;
5764

58-
if (isResource() && TextUtils.isEmpty(mUri.toString())) {
65+
if (isResourceMissing) {
5966
throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'.");
6067
}
6168

62-
if (isLocalResourceUri(mUri)) {
63-
// Convert res:/ scheme to android.resource:// so
64-
// glide can understand the uri.
65-
mUri = Uri.parse(mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/"));
69+
boolean shouldConvertLocalResourceScheme = mUri != null && isLocalResourceUri(mUri);
70+
71+
if (shouldConvertLocalResourceScheme) {
72+
// Convert res:/ scheme to android.resource:// so glide can understand the uri.
73+
String convertedUri = mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/");
74+
mUri = Uri.parse(convertedUri);
75+
}
76+
}
77+
78+
/**
79+
* Attempts to find a resource URI for the given source.
80+
* First uses the URI from ImageSource (checks drawable folder),
81+
* then falls back to raw folder for assets like SVGs in release builds.
82+
*
83+
* @param context Application context
84+
* @param source Original source string
85+
* @param initialUri URI resolved by ImageSource
86+
* @return Resolved URI or null if not found
87+
*/
88+
@Nullable
89+
private Uri resolveResourceUri(Context context, String source, Uri initialUri) {
90+
boolean isInitialUriValid = initialUri != null && !TextUtils.isEmpty(initialUri.toString());
91+
92+
if (isInitialUriValid) {
93+
return initialUri;
94+
}
95+
96+
boolean isSourceNull = source == null;
97+
boolean isRemoteUrl = source != null && source.startsWith(HTTP_SCHEME_PREFIX);
98+
boolean isDataUri = source != null && source.startsWith(DATA_URI_PREFIX);
99+
boolean shouldSkipRawLookup = isSourceNull || isRemoteUrl || isDataUri;
100+
101+
if (shouldSkipRawLookup) {
102+
return initialUri;
103+
}
104+
105+
return tryResolveFromRawFolder(context, source);
106+
}
107+
108+
/**
109+
* Attempts to find a resource in the res/raw folder.
110+
* This is needed because SVG and some other assets are bundled to res/raw/
111+
* in Android release builds, but ImageSource only checks res/drawable/.
112+
*
113+
* @param context Application context
114+
* @param source Original source string
115+
* @return URI pointing to raw resource, or null if not found
116+
*/
117+
@Nullable
118+
private Uri tryResolveFromRawFolder(Context context, String source) {
119+
String rawResourceName = toAndroidResourceName(source);
120+
int rawResId = context.getResources().getIdentifier(
121+
rawResourceName,
122+
RAW_RESOURCE_FOLDER,
123+
context.getPackageName()
124+
);
125+
126+
boolean resourceNotFound = rawResId == 0;
127+
128+
if (resourceNotFound) {
129+
return null;
66130
}
131+
132+
String rawResourceUri = ANDROID_RESOURCE_SCHEME + "://" +
133+
context.getPackageName() + "/" + RAW_RESOURCE_FOLDER + "/" + rawResourceName;
134+
135+
return Uri.parse(rawResourceUri);
67136
}
68137

138+
/**
139+
* Converts a source name to Android resource name format.
140+
* Android resources must be lowercase and cannot contain hyphens.
141+
* Hyphens are removed to match React Native's bundling behavior.
142+
*
143+
* @param source Original source name
144+
* @return Android-compatible resource name
145+
*/
146+
private String toAndroidResourceName(String source) {
147+
return source.toLowerCase().replace("-", "");
148+
}
69149

70150
public boolean isBase64Resource() {
71151
return mUri != null && FastImageSource.isBase64Uri(mUri);

0 commit comments

Comments
 (0)