Everything that was written in this article originally was caused by a nasty bug in my application, which lead me to wrongly believe there is some problem with how FileSystem.cachDirectory works in Expo.
Before you proceed any further, consider reading the following article which explains the issue:
https://www.echowaves.com/post/a-bug-that-made-me-a-better-developer
I have built a mobile app in Expo which is all about sharing photos https://www.wisaw.com/ -- it's very heavy on serving images, and it needs to do it super fast. Since react-native-fast-image is not available in Expo managed workflow, I had to implement my own caching solution, which worked extremely well at first, but then... my app started to crash!!!
I spent days chasing the issue, and the only thing I could link it to was the Expo's FileSystem.cacheDirectory.
This is especially said, because I always assumed, that device's OS has to take care of maintaining the right balance between the amounts of info stored in cache folder and the health of the system.
By trial an error I found, that, when the app starts to crash eventually, the only way to get it back to working state is to re-install it from the store, after which it will work for awhile again, usually for couple of weeks, and then the cycle repeats. I can't expect my customers to re-install the app every time it starts to crash. The next time it started to happened again, I tried to wipe the cacheFolder via pushing over-the-Air update, instead of re-installing -- and it fixed it! Great -- I'm on the right track.
So, here is the dilemma -- I can't expect my customers to re-instal the app every couple of weeks, but I can't serve all the images without cache either. There has to be a compromise solution.
As the result I wrote a better version of the function which cleans up the cache folder. The function is invoked on the app start, keeping up to 8000 files that were most recently cached, removing the rest.
Here is the implementation:
export const IMAGE_CACHE_FOLDER = `${FileSystem.cacheDirectory}images/`
export const cleanupCache = () => async (dispatch, getState) => {
// _checkUploadDirectory()
const cacheDirectory = await FileSystem.getInfoAsync(CONST.IMAGE_CACHE_FOLDER)
// create cacheDir if does not exist
if (!cacheDirectory.exists) {
await FileSystem.makeDirectoryAsync(CONST.IMAGE_CACHE_FOLDER)
}
if (Platform.OS === 'ios') {
// cleanup old cached files
const cachedFiles = await FileSystem.readDirectoryAsync(`${CONST.IMAGE_CACHE_FOLDER}`)
let position = 0
let results = []
const batchSize = 10
// batching promise.all to avoid exxessive promisses call
while (position < cachedFiles.length) {
const itemsForBatch = cachedFiles.slice(position, position + batchSize)
results = [...results, ...await Promise.all(itemsForBatch.map(async file => {// eslint-disable-line
const info = await FileSystem.getInfoAsync(`${CONST.IMAGE_CACHE_FOLDER}${file}`)// eslint-disable-line
return Promise.resolve({ file, modificationTime: info.modificationTime, size: info.size })
}))]
position += batchSize
}
// cleanup cache, leave only 5000 most recent files
const sorted = results
.sort((a, b) => a.modificationTime - b.modificationTime)
for (let i = 0; sorted.length - i > 8000; i += 1) { // may need to reduce down to 500
FileSystem.deleteAsync(`${CONST.IMAGE_CACHE_FOLDER}${sorted[i].file}`, { idempotent: true })
}
}
}
The implementation is pretty straight forward and self explanatory. To view the source, check it out in my git repo:
https://github.com/echowaves/WiSaw/blob/master/src/screens/PhotosList/reducer.js#L674
or expo slack:
https://snack.expo.io/@dmitryame/cleanup-expo-cache-folder-
Thanks for reading.
Top comments (0)