- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
After implementing a manual OTA (Over-The-Air) update system for my React Native app, I ran into a major limitation: images imported via require() don’t update with the JS bundle. React Native statically bundles assets during build time, so even after a successful OTA update, the images remained stale.
To solve this, I created a flexible system where image assets are:
Here’s exactly how I did it:
? Step 1: Organizing the Asset Bundle (ota-bundles)
My folder structure looks like this:
?ota-bundles/
┣ ?drawable-mdpi/
┃ ┣ ?assets_workoutimage_triceps_tricepdips.webp
┃ ┣ ?node_modules_reactnativecalendars_src_img_down.png
┃ ┗ ?... more images
┣ ?drawable-hdpi/
┣ ?drawable-xhdpi/
┣ ?drawable-xxhdpi/
┣ ?drawable-xxxhdpi/
┣ ?index.android.bundle ← OTA JS bundle
┣ ?version.json ← Version tracking file
┣ ?index.html, 404.html ← Firebase placeholders
┗ ?ota-bundles.zip ← Zipped OTA bundle
Inside each drawable-* folder, I placed all relevant image assets including custom app images and auto-extracted images used by third-party libraries (like react-native-calendars, react-navigation, etc.).
? Step 2: Zipping the Asset Bundle
Once my folder structure was ready, I zipped the entire ota-bundles folder:
zip -r ota-bundles.zip ota-bundles/
This produced ota-bundles.zip, which contains:
This .zip file is what my app will download during an update.
Step 3: Hosting the Zip File on Firebase Hosting
Firebase Hosting provides a reliable and fast CDN for serving static files.
Setup
firebase deploy --only hosting
This URL is then used in the app to download the update.
? Step 4: Downloading & Unzipping the Assets at Runtime
In your React Native app, you handle this zip file using react-native-fs and react-native-zip-archive:
import RNFS from 'react-native-fs';
import { unzip } from 'react-native-zip-archive';
const ZIP_URL = '
const LOCAL_ZIP = `${RNFS.DocumentDirectoryPath}/ota-bundles.zip`;
const EXTRACT_PATH = `${RNFS.DocumentDirectoryPath}/ota-bundles`;
export const downloadAndExtractBundle = async () => {
// Step 1: Download ZIP
const downloadRes = await RNFS.downloadFile({
fromUrl: ZIP_URL,
toFile: LOCAL_ZIP,
}).promise;
if (downloadRes.statusCode === 200) {
// Step 2: Extract
const extractedPath = await unzip(LOCAL_ZIP, EXTRACT_PATH);
await RNFS.unlink(LOCAL_ZIP); // Clean up
console.log('Assets extracted to:', extractedPath);
} else {
console.warn('Failed to download OTA bundle');
}
};
?️ Step 5: Loading Images Dynamically
React Native’s require() doesn’t support dynamic paths. Instead, you use the file:// URI scheme to load images like this:
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import RNFS from 'react-native-fs';
const imagePath = `${RNFS.DocumentDirectoryPath}/ota-bundles/drawable-mdpi/assets_workoutimage_triceps_tricepdips.webp`;
export default function TricepsImage() {
return <Image source={{ uri: `file://${imagePath}` }} style={styles.image} />;
}
const styles = StyleSheet.create({
image: {
width: 120,
height: 120,
resizeMode: 'contain',
},
});
This works across all densities by checking PixelRatio and dynamically selecting the correct drawable-* folder if needed.
To solve this, I created a flexible system where image assets are:
- Organized by screen density (drawable-mdpi, hdpi, etc.),
- Zipped into a single file,
- Hosted on Firebase Hosting,
- Downloaded and unzipped at runtime,
- Loaded dynamically using file URIs.
Here’s exactly how I did it:
? Step 1: Organizing the Asset Bundle (ota-bundles)
My folder structure looks like this:
?ota-bundles/
┣ ?drawable-mdpi/
┃ ┣ ?assets_workoutimage_triceps_tricepdips.webp
┃ ┣ ?node_modules_reactnativecalendars_src_img_down.png
┃ ┗ ?... more images
┣ ?drawable-hdpi/
┣ ?drawable-xhdpi/
┣ ?drawable-xxhdpi/
┣ ?drawable-xxxhdpi/
┣ ?index.android.bundle ← OTA JS bundle
┣ ?version.json ← Version tracking file
┣ ?index.html, 404.html ← Firebase placeholders
┗ ?ota-bundles.zip ← Zipped OTA bundle
Inside each drawable-* folder, I placed all relevant image assets including custom app images and auto-extracted images used by third-party libraries (like react-native-calendars, react-navigation, etc.).
? Step 2: Zipping the Asset Bundle
Once my folder structure was ready, I zipped the entire ota-bundles folder:
zip -r ota-bundles.zip ota-bundles/
This produced ota-bundles.zip, which contains:
- index.android.bundle (JS code),
- drawable-* folders (images),
- version.json (to detect updates).
This .zip file is what my app will download during an update.
Firebase Hosting provides a reliable and fast CDN for serving static files.
Setup
Now host the whole ota-bundles.
Run:
firebase deploy --only hosting
- After deployment, Firebase gives you a public URL:
This URL is then used in the app to download the update.
? Step 4: Downloading & Unzipping the Assets at Runtime
In your React Native app, you handle this zip file using react-native-fs and react-native-zip-archive:
import RNFS from 'react-native-fs';
import { unzip } from 'react-native-zip-archive';
const ZIP_URL = '
const LOCAL_ZIP = `${RNFS.DocumentDirectoryPath}/ota-bundles.zip`;
const EXTRACT_PATH = `${RNFS.DocumentDirectoryPath}/ota-bundles`;
export const downloadAndExtractBundle = async () => {
// Step 1: Download ZIP
const downloadRes = await RNFS.downloadFile({
fromUrl: ZIP_URL,
toFile: LOCAL_ZIP,
}).promise;
if (downloadRes.statusCode === 200) {
// Step 2: Extract
const extractedPath = await unzip(LOCAL_ZIP, EXTRACT_PATH);
await RNFS.unlink(LOCAL_ZIP); // Clean up
console.log('Assets extracted to:', extractedPath);
} else {
console.warn('Failed to download OTA bundle');
}
};
?️ Step 5: Loading Images Dynamically
React Native’s require() doesn’t support dynamic paths. Instead, you use the file:// URI scheme to load images like this:
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import RNFS from 'react-native-fs';
const imagePath = `${RNFS.DocumentDirectoryPath}/ota-bundles/drawable-mdpi/assets_workoutimage_triceps_tricepdips.webp`;
export default function TricepsImage() {
return <Image source={{ uri: `file://${imagePath}` }} style={styles.image} />;
}
const styles = StyleSheet.create({
image: {
width: 120,
height: 120,
resizeMode: 'contain',
},
});
This works across all densities by checking PixelRatio and dynamically selecting the correct drawable-* folder if needed.