Build for iOS device do not load existing SQLite database from bundled resource
Build/Submit details page URL
https://expo.dev/accounts/cnptia-dev/projects/sqlite-app/builds/46681620-686b-4f29-a39b-4c3c86d0dcb3
Summary
When building an app which reads an existing SQLite database and available in assets folder of the app I get an blank screen on iOS Simulator.
I builded a demo app and let it available in GitHub.
I identified this problem in another app with same feature (read existing SQLite database) and no data is returned.
On MacOS:
- when I run
expo start --localhostand deploy to an Simulator, the app works ok. - when I run
expo prebuild --platform iosand deploy to an Simulator, the app works ok. - when I run
eas build -p ios --profile previeworeas build -p ios --profile production, and install the app on a device or Simulator, the app do not work.
My network is behind corporate proxy.
Managed or bare?
Managed
Environment
$ npx expo-env-info expo-env-info 1.0.5 environment info: System: OS: Linux 5.15 Linux Mint 21 (Vanessa) Shell: 5.1.16 - /bin/bash Binaries: Node: 16.17.0 - /usr/bin/node Yarn: 1.22.19 - ~/.yarn/bin/yarn npm: 8.18.0 - /usr/bin/npm npmPackages: expo: ~46.0.9 => 46.0.9 react: 18.0.0 => 18.0.0 react-native: 0.69.5 => 0.69.5 npmGlobalPackages: eas-cli: 1.2.0 expo-cli: 6.0.5 Expo Workflow: managed
$ expo doctor
Warning: could not reach expo.io.
Warning: could not reach expo.fyi.
Warning: could not reach expo.dev.
Warning: could not reach static.expo.dev.
Warning: could not reach exp.host.
We couldn't reach some of our domains, this might cause issues on our website or services. Please check your network configuration and try to access these domains in your browser.
🎉 Didn't find any issues with the project!
Error output
$ EXPO_DEBUG=true expo doctor
- Finding all copies of expo-modules-autolinking All copies of expo-modules-autolinking satisfy ~0.10.1
- Finding all copies of @expo/config-plugins All copies of @expo/config-plugins satisfy ^5.0.0
- Finding all copies of @expo/prebuild-config All copies of @expo/prebuild-config satisfy ^5.0.1
- Finding all copies of @unimodules/core No dependencies found for @unimodules/core
- Finding all copies of @unimodules/react-native-adapter No dependencies found for @unimodules/react-native-adapter
- Finding all copies of react-native-unimodules
No dependencies found for react-native-unimodules
Warning: could not reach
expo.io. Warning: could not reachexpo.fyi. Warning: could not reachexpo.dev. Warning: could not reachstatic.expo.dev. Warning: could not reachexp.host.
We couldn't reach some of our domains, this might cause issues on our website or services. Please check your network configuration and try to access these domains in your browser.
🎉 Didn't find any issues with the project!
Reproducible demo or steps to reproduce from a blank project
GitHub
git clone https://github.com/severnini/sqlite-app.git
cd sqlite-app
npm install
eas build -p ios --profile preview
After the build finishes, download the tar.gz generated, extract and install in an iOS Simulator.
@severnini we are experiencing same issue
After some debugging I found a solution for this.
Android
When we call Asset.fromModule(...) the file is not yet downloaded to the device's cache folder.
Example:
Destination: file:///data/user/0/host.exp.exponent/files/ExperienceData/%2540user%252Fsqlite-app/SQLite/data.db
Asset (converted to json) BEFORE download:
{"hash":"hash-removed-for-example","localUri":null,"width":null,"height":null,"downloading":false,"downloaded":false,"_downloadCallbacks":[],"name":"data","type":"db","uri":"http://127.0.0.1:19000/assets/assets/data/data.db?platform=android&hash=hash-removed-for-example?platform=android&dev=true&hot=false"}
Asset (converted to json) AFTER download:
{"hash":"hash-removed-for-example","localUri":"file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540user%252Fsqlite-app/ExponentAsset-hash-removed-for-example.db","width":null,"height":null,"downloading":false,"downloaded":true,"_downloadCallbacks":[],"name":"data","type":"db","uri":"http://127.0.0.1:19000/assets/assets/data/data.db?platform=android&hash=hash-removed-for-example?platform=android&dev=true&hot=false"}
iOS
When we do the same call, the file is already download to the device's cache folder.
Example:
Destination: file:///Users/user/Library/Developer/CoreSimulator/Devices/00674C00-78C3-4658-9FDB-5ACF5AC18382/data/Containers/Data/Application/9FACA5B1-1C86-4D65-8006-6280339A7041/Documents/SQLite/data.db
Asset (converted to json) just AFTER call Asset.fromModule(...):
{"hash":"hash-removed-for-example", "localUri": "file:///Users/user/Library/Developer/CoreSimulator/Devices/00674C00-78C3-4658-9FDB-5ACF5AC18382/data/Containers/Data/Application/9FACA5B1-1C86-4D65-8006-6280339A7041/Library/Application Support/.expo-internal/hash-removed-for-example.db","width":null,"height":null," downloading":false,"downloaded":true,"_downloadCallbacks":[],"name":"data","type":"db","uri":"https://classic-assets.eascdn.net/~assets/hash-removed-for-example"}
The strategy
-
Call
Asset.fromModule(...) -
Check if is not downloaded
2.1. then call asset.downloadAsync()
2.2. after download finishes, just copy the .db file to the SQLite document directory
- If not yet downloaded
3.1. Check for assset.localUri or if is an asset or file resource
3.1.1. If so, just copy the .db file to the SQLite document directory
3.2. Check if asset.uri is a network resource (http or https)
3.2.1. If so, download the .db file to the SQLite document directory
The code
async function openDatabase() {
const localFolder = FileSystem.documentDirectory + 'SQLite'
const dbName = 'data.db'
const localURI = localFolder + '/' + dbName
if (!(await FileSystem.getInfoAsync(localFolder)).exists) {
await FileSystem.makeDirectoryAsync(localFolder)
}
let asset = Asset.fromModule(require('./assets/data/data.db'))
if (!asset.downloaded) {
await asset.downloadAsync().then(value => {
asset = value
console.log('asset downloadAsync - finished')
})
let remoteURI = asset.localUri
await FileSystem.copyAsync({
from: remoteURI,
to: localURI
}).catch(error => {
console.log('asset copyDatabase - finished with error: ' + error)
})
} else {
// for iOS - Asset is downloaded on call Asset.fromModule(), just copy from cache to local file
if (asset.localUri || asset.uri.startsWith("asset") || asset.uri.startsWith("file")) {
let remoteURI = asset.localUri || asset.uri
await FileSystem.copyAsync({
from: remoteURI,
to: localURI
}).catch(error => {
console.log("local copyDatabase - finished with error: " + error)
})
} else if (asset.uri.startsWith("http") || asset.uri.startsWith("https")) {
let remoteURI = asset.uri
await FileSystem.downloadAsync(remoteURI, localURI)
.catch(error => {
console.log("local downloadAsync - finished with error: " + error)
})
}
}
return SQLite.openDatabase(dbName)
}
PS: the code is commited to github example.
Anyway, may be there is a better solution, but for now this is the way I found to have an SQLite database loaded in both Android and iOS app version.
Please feel free to comment and put some insight.
@severnini Your solution worked for me! Thank you!