Reputation: 49
On Android it hasn't shown to be an issue but iOS takes a full minute to display images when taken with camera/gallery to create a new post AND also in a FlatList
render.
Has anyone else had this issue or know how to optimize it?
I am using xhr
to convert to a blob which I am wondering if that might be the issue because I do set a loading spinner which doesn't show for a minute then it does and it loads fast.
This is a react native app built with expo using Firebase storage I'm converting the image to a download URL and saving to a Firestore document.
Here is my image handler:
function ImageSelector({
onTakeImage,
editImage,
basePath,
clearImage,
}: {
onTakeImage: (image: ImageType) => void
clearImage: boolean
editImage?: {url: string, type: string} | null
basePath: string
}) {
const [image, setImage] = useState<{
url: string
type: string
} | null>(editImage ? editImage : null)
const [showDurationErr, setShowDurationErr] = useState<boolean>(false)
const [galleryImage, setGalleryImage] = useState<{
url: string
type: string
} | null>(null)
const [cameraPermissionInfo, requestPermission] = useCameraPermissions()
const [mediaStatus, requestPermissionMedia] = useMediaLibraryPermissions()
const [uploadStatus, setUploadStatus] = useState<number>(-1)
const video = useRef(null)
const [status, setStatus] = useState<AVPlaybackStatusSuccess | {}>({})
async function verifyPermissions() {
if (cameraPermissionInfo?.status === PermissionStatus.UNDETERMINED) {
const permission = await requestPermission()
return permission.granted
}
if (cameraPermissionInfo?.status === PermissionStatus.DENIED) {
Alert.alert(
"Permission was denied",
"Camera permissions are required to use this app."
)
return false
}
return true
}
useEffect(() => {
if (clearImage) {
setImage(null)
}
}, [clearImage])
useEffect(() => {
if (!cameraPermissionInfo?.granted) verifyPermissions()
if(!mediaStatus?.granted) verifyGalleryPermissions()
}, [])
async function verifyGalleryPermissions() {
if (mediaStatus?.status === PermissionStatus.UNDETERMINED) {
const permission = await requestPermissionMedia()
return permission.granted
}
if (mediaStatus?.status === PermissionStatus.DENIED) {
Alert.alert(
"Permission was denied",
"Gallery permissions will be needed to use this app."
)
return false
}
return true
}
async function imageTakerHandler() {
const hasPermission = await verifyPermissions()
if (!hasPermission) {
return
}
const generatedFileId = uuid.v4()
const image = await launchCameraAsync({
mediaTypes: MediaTypeOptions.All,
allowsEditing: true,
// aspect: [16, 9],
quality: 0.6,
})
console.log(image)
async function getDownloadURL(){
console.log(image)
if(image.canceled){
setShowDurationErr(true)
return
}
if (image.assets) {
//fetch and react native - in RN .57+ swtiched the default xhr.responseType from blob to text thus blobModules uriandler doesnt kick in and since
//okhttp doesnt recognize url of file:// scheme it throws and exception. Something about a blob leak so use blob.close()
try {
const uri = image.assets[0].uri
const blob = await new Promise((resolve, reject) => {
Toast.show({
type: "success",
text1: "your blob",
text2: 'has started',
})
const xhr = new XMLHttpRequest()
xhr.onload = function () {
resolve(xhr.response)
}
xhr.onerror = function () {
reject(new TypeError("Network request failed"))
}
xhr.responseType = "blob"
xhr.open("get", uri, true)
xhr.send(null)
})
const downloadUrl = await FirebaseStorageService.uploadFile(
blob as Blob,
`${basePath}/${generatedFileId}`,
setUploadStatus
)
return downloadUrl
} catch (error: any) {
console.log(error?.message, "downloadImageurl error")
}finally{
Toast.show({
type: "congrats",
text1: "your blob",
text2: 'has completed',
})
}
}
}
getDownloadURL().then((response) => {
if (response && image.assets) {
setImage({
url: response as string,
type: image.assets[0].type!,
})
onTakeImage({
url: response as string,
type: image.assets[0].type!,
})
}
}).then(() => setUploadStatus(-1))
}
async function imageSelectorHandler() {
setShowDurationErr(false)
setGalleryImage(null)
const hasPermissionforGallery = await verifyGalleryPermissions()
if (!hasPermissionforGallery) {
return
}
const generatedFileId = uuid.v4()
const galleryImage = await launchImageLibraryAsync({
mediaTypes: MediaTypeOptions.All,
allowsEditing: true,
// aspect: [16, 9],
quality: 0.6,
videoMaxDuration: 120,
})
async function getDownloadURL() {
if (
galleryImage.canceled ||
!galleryImage.assets ||
(galleryImage.assets[0].duration &&
galleryImage.assets[0]?.duration > 150000)
) {
// User cancelled the image selection or no assets were returned
setShowDurationErr(true)
return
}
if (galleryImage.assets) {
//fetch and react native - in RN .57+ swtiched the default xhr.responseType from blob to text thus blobModules uriandler doesnt kick in and since
//okhttp doesnt recognize url of file:// scheme it throws and exception. Something about a blob leak so use blob.close()
try {
const uri = galleryImage.assets[0].uri
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = function () {
resolve(xhr.response)
}
xhr.onerror = function () {
reject(new TypeError("Network request failed"))
}
xhr.responseType = "blob"
xhr.open("get", uri, true)
xhr.send(null)
})
const downloadUrl = await FirebaseStorageService.uploadFile(
blob as Blob,
`${basePath}/${generatedFileId}`,
setUploadStatus
)
return downloadUrl
} catch (error: any) {
console.log(error?.message, "downloadImageurl error")
}
}
}
getDownloadURL().then((response) => {
if (response && galleryImage.assets) {
setGalleryImage({
url: response as string,
type: galleryImage.assets[0].type!,
})
onTakeImage({
url: response as string,
type: galleryImage.assets[0].type!,
})
}
}).then(() => setUploadStatus(-1))
}
let imagePreview = (
<Text
style={{
color: Colors.bone,
marginVertical: 10,
fontFamily: "Prata",
fontSize: 17,
}}
>
Image/Video
</Text>
)
if (image) {
imagePreview = (
<View style={styles.imagePrev}>
<Image style={styles.image} source={{ uri: image.url }} />
</View>
)
}
if (galleryImage) {
if (galleryImage.type === "image") {
imagePreview = (
<View style={styles.imagePrev}>
<Image
resizeMode="contain"
style={styles.image}
source={{ uri: galleryImage.url }}
/>
</View>
)
} else if (galleryImage.type === "video") {
imagePreview = (
<View className="h-80 w-full mb-2 ">
<Video
ref={video}
style={{ width: "100%", height: "100%" }}
source={{
uri: galleryImage.url,
}}
useNativeControls
resizeMode={ResizeMode.COVER}
isLooping
onPlaybackStatusUpdate={(status) =>
setStatus(() => status)
}
/>
</View>
)
}
}
return (
<ScrollView className="w-full">
<View className="w-full">{imagePreview}</View>
{uploadStatus > 0 &&
<Progress.Circle
className="self-center"
progress={uploadStatus}
animated={true}
showsText={true}
unfilledColor={Colors.primaryGreen}
color={Colors.accentDark}
/>}
<View style={styles.buttonContainer}>
<Icon
color={Colors.bone}
name={"camera-enhance-outline"}
onPress={imageTakerHandler}
size={30}
text="Camera"
/>
{/* <PrimaryBtn text="Camera" onPress={imageTakerHandler} /> */}
<Icon
color={Colors.primaryLight}
name={"view-gallery-outline"}
onPress={imageSelectorHandler}
size={30}
text="Gallery"
/>
{/* <AccentBtn text="Gallery" onPress={imageSelectorHandler} /> */}
</View>
{showDurationErr && (
<Text className="text-center text-bone">
Something went wrong, if video is longer than allotment of 2.5 minutes, please trim
first.
</Text>
)}
</ScrollView>
)
}
Upvotes: 2
Views: 257