- SDK Version: 40
- Platforms(Android):
- #sdk #expo-client #filesystem
I am using FileSystem to cache images to load them faster.
So what I do is download the image using downloadAsync and save it in the cache directory. Whenever user updates the new image (Profile picture) I use deleteAsync to clear the old cache so that next time latest file gets downloaded and get cached so that if user navigates between pages the cached image gets displayed. So cache gets clean only in one case i.e. when user updates the profile pic.
Issue: The issue I am facing on android is that when ever user updates the new image on android the image gets uploaded successfully and deletecacheasync cleans the cache also and user gets navigated to different page but when user comes back to that page again the download function checks if the same file exists in the cache and the metadata returns false and then it downloads the new file but the old image gets displayed.
Here is the code to download
const fileURI = `${FileSystem.cacheDirectory}profile`;
const downloadImage = async (uri) => {
try {
// Use the cached image if it exists
const metadata = await FileSystem.getInfoAsync(fileURI);
// debugger;
if (!metadata.exists) {
// download to cache
if (componentIsMounted.current) {
setImage(null)
await FileSystem.downloadAsync(
uri,
fileURI
)
}
if (componentIsMounted.current) {
console.log('Downloaded ' + fileURI);
setImage(fileURI)
makeBlob(fileURI);
}
} else {
if (componentIsMounted.current && metadata && metadata.uri) {
console.log('Existing ' + metadata.uri);
setImage(metadata.uri);
makeBlob(metadata.uri);
}
}
} catch (err) {
console.log() // eslint-disable-line no-console
setImgURI(uri)
}
}
The const fileUri I have declared outside the component function body in the same file.
Code to delete the cache
await FileSystem.deleteAsync(fileURI);
Here is the Code
const fileURI = `${FileSystem.cacheDirectory}profile`;
const EditProfileScreen = (props) => {
const dispatch = useDispatch();
const [image, setImage] = useState('https://api.adorable.io/avatars/80/abott@adorable.png');
const { colors } = useTheme();
const [blob, setblob] = useState(null);
const componentIsMounted = useRef(true);
const profileData = useSelector((state) => {
return (state && state.profile && state.profile.user) ? state.profile.user : {}
})
const downloadImage = async (uri) => {
try {
// Use the cached image if it exists
const metadata = await FileSystem.getInfoAsync(fileURI);
// debugger;
if (!metadata.exists) {
// download to cache
if (componentIsMounted.current) {
setImage(null)
await FileSystem.downloadAsync(
uri,
fileURI
)
}
if (componentIsMounted.current) {
console.log('Downloaded ' + fileURI);
setImage(fileURI)
makeBlob(fileURI);
}
} else {
if (componentIsMounted.current && metadata && metadata.uri) {
console.log('Existing ' + metadata.uri);
setImage(metadata.uri);
makeBlob(metadata.uri);
}
}
} catch (err) {
console.log() // eslint-disable-line no-console
setImgURI(uri)
}
}
const makeBlob = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
setblob(blob);
}
useEffect(() => {
if (profileData && profileData.profilePic) {
console.log(profileData.profilePic);
downloadImage(profileData.profilePic);
console.log(`${FileSystem.cacheDirectory}profile`);
}
}, [profileData])
useEffect(() => {
(
async () => {
if (Platform.OS !== "web") {
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Permission denied !')
}
}
}
)();
return () => {
console.log('Component unmounted');
componentIsMounted.current = false
}
}, [])
const {
handleChange,
handleSubmit,
handleBlur,
values,
errors,
touched,
resetForm,
isValidating
} = useFormik({
validationSchema: profileValidationSchema,
initialValues: {
},
enableReinitialize: true,
validate: (err) => {
return {};
},
onSubmit: async (values) => {
console.log(values);
var formData = new FormData();
if (blob) {
formData.append("FileObject", { uri: image, name: `profile-${+new Date()}.jpg`, type: "image/jpeg" });
}
dispatch(showLoader(true));
dispatch(updateUserProfile(formData))
.then( async (data) => {
dispatch(showLoader(false));
try {
await FileSystem.deleteAsync(fileURI);
const metadata = await FileSystem.getInfoAsync(fileURI);
} catch (error) {
console.log('error');
}
props.navigation.pop();
}).catch((err) => {
dispatch(showLoader(false));
});
},
});
const takePhotoFromCamera = async () => {
// console.log('Test');
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 0.3
})
// console.log(result);
// const response = await fetch(result.uri);
// const blob = await response.blob();
// setblob(blob);
if (!result.cancelled) {
bs.current.snapTo(1);
const response = await fetch(result.uri);
const blob = await response.blob();
setblob(blob);
setImage(result.uri);
}
}
const choosePhotoFromLibrary = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1
})
// const response = await fetch(result.uri);
// const blob = await response.blob();
// setblob(blob);
// console.log(blob);
if (!result.cancelled) {
bs.current.snapTo(1)
const response = await fetch(result.uri);
const blob = await response.blob();
setblob(blob);
setImage(result.uri);
}
}
return (
<View style={{ alignItems: 'center' }}>
<TouchableOpacity>
<View
style={{
height: 100,
width: 100,
borderRadius: 15,
justifyContent: 'center',
alignItems: 'center',
}}>
<ImageBackground
source={{
uri: image,
}}
style={{ height: 100, width: 100 }}
imageStyle={{ borderRadius: 15 }}>
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Icon
name="camera"
size={35}
color="#fff"
style={{
opacity: 0.7,
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: '#fff',
borderRadius: 10,
}}
/>
</View>
</ImageBackground>
</View>
</TouchableOpacity>
</View>
);
};
This whole thing works fine on iOS but not on android.
I checked it on both expo and standalone apps.