expo-file-system .downloadAsync leads to half-gray corrupted images

Viewed 156

IMAGE OF THE PROBLEM This is part of a custom Cached Image component. It will sometimes render a corrupted image on first load and no previous cache, when it is first downloaded. As far as I know this happens only on Android and could be related to internet speed, but of that I am not sure. The code is:


import { Image } from 'react-native'

import * as FileSystem from 'expo-file-system'

import PropTypes from 'prop-types'

const NoImage = require('../../assets/NoImage.png');

const CachedImage = props => {

    const { source: { uri }, cacheKey } = props
    let filesystemURI = `${FileSystem.cacheDirectory}${cacheKey}`

    const [imgSOURCE, setImgSOURCE] = useState({ uri: filesystemURI })
    const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

    const componentIsMounted = useRef(true)

    useEffect(() => {
        componentIsMounted.current = true;
        if (componentIsMounted.current) {
            setImgSOURCE({ uri: filesystemURI })
        }
        return () => {
            componentIsMounted.current = false
        }
    }, [cacheKey])

    useEffect(() => {
        filesystemURI = `${FileSystem.cacheDirectory}${cacheKey}`

        const loadImage = async ({ fileURI }) => {
            try {
                // Use the cached image if it exists
                const metadata = await FileSystem.getInfoAsync(fileURI).catch(function (error) {
                    console.log('There has been a problem with the getInfo operation: ' + error.message);
                    // ADD THIS THROW error
                    throw error;
                });
                if (!metadata.exists) {
                    // download to cache
                    if (componentIsMounted.current) {
                        setImgSOURCE(NoImage)
                        FileSystem.downloadAsync(
                            uri,
                            fileURI
                        ).then(({ uri, status, headers }) => {
                            if (componentIsMounted.current) {
                                //sleep(100);
                                setImgSOURCE({ uri: uri })
                            }
                        })
                            .catch(function (error) {
                                console.log('There has been a problem with your fetch operation: ' + error.message);
                                // ADD THIS THROW error
                                throw error;
                            });
                    }
                }
            } catch (err) {
                console.log('cache image error:' + err)
                if (componentIsMounted.current) {
                    setImgSOURCE({ uri: uri })
                }
            }
        }

        loadImage({ fileURI: filesystemURI })

        return () => {
            componentIsMounted.current = false
        }
    }, [cacheKey])

    return (
        <Image
            {...props}
            onError={() => { setImgSOURCE(NoImage) }}
            source={imgSOURCE}
        />
    )
}

CachedImage.propTypes = {
    source: PropTypes.object.isRequired,
    cacheKey: PropTypes.string.isRequired,
}

export default CachedImage

I have added a commented-out sleep before the setState for the image but haven't properly tested it. expo-file-system version is 13.1.3. It has also happened to other people: React-native expo FileSystem.downloadAsync corrupted images

1 Answers

We face the same issue with Expo SDK45 and expo-file-system 14.0.0. I have the following logic in place (see code snippet) and can confirm that onError is never triggered and we still see a partial image sometimes and not the 'missing.png'. My current assumption is, that this is rather an image caching than image loading issue. Usually the image displays ok after a refresh of the screen.

import imgNone from '../../assets/images/missing.png';

    const [imgSrc, setImgSrc] = useState(imageSrc(element.media[0]))

                <View>
                    <Image key={element.media[0]} source={imgSrc} onError={() => setImgSrc(imgNone)} alt={i18n.t('global.missingImage')} size='sm'/>
                </View>

Forgot to mention: I did some fiddling with image width and height and was able to display 'old' versions of the image after the image was reloaded. That as well led to the image caching assumption.

Related