expo / expo/examples

White Blank Screen

Open
#628 8 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
3.7k
Forks
1.1k
Avg merge
11h 15m
Merged PRs (30d)
1

Description

I have noticed that when i take a picture in a bright light most times, my screen turns white requesting me to restart the app. Here is my hook

`import {
CameraRecordingOptions,
CameraType,
CameraView,
useCameraPermissions,
} from "expo-camera";
import * as MediaLibrary from "expo-media-library";
import { useCallback, useEffect, useRef, useState } from "react";

export function useCamera() {
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
const [mediaPermission, requestMediaPermission] =
MediaLibrary.usePermissions();

const cameraRef = useRef(null);
const [isReady, setIsReady] = useState(false);
const [type, setType] = useState("front");
const [mode, setMode] = useState<"photo" | "video">("photo");
const [isRecording, setIsRecording] = useState(false);

// ✅ Now managed inside the hook
const [photoUri, setPhotoUri] = useState(null);
const [videoUri, setVideoUri] = useState(null);

// Request permissions at startup
useEffect(() => {
if (!cameraPermission?.granted) {
requestCameraPermission();
}
if (!mediaPermission?.granted) {
requestMediaPermission();
}
}, [
cameraPermission,
mediaPermission,
requestCameraPermission,
requestMediaPermission,
]);

const toggleCameraType = useCallback(() => {
setType((prev) => (prev === "back" ? "front" : "back"));
}, []);

const toggleMode = useCallback(() => {
setMode((prev) => (prev === "photo" ? "video" : "photo"));
}, []);

const takePicture = useCallback(
async (options?: { base64?: boolean }) => {
if (!cameraRef.current || !isReady || mode !== "photo") {
return null;
}
try {
const photo = await cameraRef.current.takePictureAsync({
quality: 0.5,
base64: options?.base64 ?? false,
skipProcessing: true,
});
setPhotoUri(photo.uri); // ✅ store inside hook
return photo;
} catch (err) {
console.error("Error taking picture:", err);
return null;
}
},
[isReady, mode]
);

const startRecording = useCallback(async () => {
if (!cameraRef.current || !isReady || mode !== "video" || isRecording) {
return null;
}
try {
setIsRecording(true);
const opts: CameraRecordingOptions = {
maxDuration: 60, // seconds
};
const video = await cameraRef.current.recordAsync(opts);
if (video && video.uri) {
setVideoUri(video.uri); // ✅ store inside hook
}
setIsRecording(false);
return video;
} catch (err) {
console.error("Error recording video:", err);
setIsRecording(false);
return null;
}
}, [isReady, mode, isRecording]);

const stopRecording = useCallback(() => {
if (cameraRef.current && isRecording) {
cameraRef.current.stopRecording();
setIsRecording(false);
}
}, [isRecording]);

// Reset captured media
const resetMedia = useCallback(() => {
setPhotoUri(null);
setVideoUri(null);
}, []);

return {
cameraRef,
hasPermission: cameraPermission?.granted,
requestCameraPermission,
hasMediaPermission: mediaPermission?.granted,
requestMediaPermission,
isReady,
setIsReady,
type,
toggleCameraType,
mode,
toggleMode,
takePicture,
startRecording,
stopRecording,
isRecording,
photoUri, // ✅ exposed state
videoUri, // ✅ exposed state
resetMedia,
};
}
`

Here is my implementation

`import CancelBlueCircle from "@/assets/svg/cancel-blue-circle.svg";
import CheckBlue3 from "@/assets/svg/check-blue-3.svg";
import { images } from "@/constants/images";
import { useLocationContext } from "@/context/LocationContext";
import { useNetwork } from "@/context/NetworkContext";
import { useStaffFaceClockin } from "@/hooks/queries/clockin";
import { useCamera } from "@/hooks/useCamera";
import { cn } from "@/utils/clsx";
import { getDeviceId } from "@/utils/getDeviceID";
import { getDeviceInfo } from "@/utils/getDeviceInfo";
import { CameraView } from "expo-camera";
import { LinearGradient } from "expo-linear-gradient";
import React, { useCallback } from "react";
import {
ActivityIndicator,
Alert,
Dimensions,
Image,
Platform,
StatusBar,
Text,
TouchableOpacity,
View,
} from "react-native";
import Modal from "react-native-modal";
import CustomButton2 from "../CustomButton2";

const { width, height } = Dimensions.get("window");

const FaceIDClockinModal = ({
isVisible,
setIsVisible,
}: {
isVisible: boolean;
setIsVisible: React.Dispatch>;
}) => {
const { locationReady, location } = useLocationContext();
const { ip } = useNetwork();
const {
cameraRef,
hasPermission,
requestCameraPermission,
setIsReady,
type,
takePicture,
photoUri,
resetMedia,
} = useCamera();

const { clockinWithFace, clockingInWithFace } = useStaffFaceClockin({
onSuccess: (values) => {
Alert.alert("Success", values.message);
onCancelPress();
},
onError: (message) => {
Alert.alert("Attention", message);
},
});

const onCancelPress = () => {
resetMedia();
setIsVisible(false);
};

const handleClockin = useCallback(async () => {
if (!locationReady) {
Alert.alert(
"Location Unavailable",
"Your GPS may be turned off. Please check your settings and try again.",
[{ text: "OK" }]
);
return;
}

if (!photoUri) {
//no image available
Alert.alert("Attention", "No captured image found");
return false;
}

if (!(await getDeviceId())) {
//no device id found
Alert.alert("Attention", "Device ID not found");
return false;
}

if (!getDeviceInfo().deviceName) {
//no device name found
Alert.alert("Attention", "Device name not found");
return false;
}

//instantiate formData
const formData = new FormData();

//add all images to formData
const fileName = photoUri.split("/").pop();
const ext = fileName?.split(".").pop()?.toLowerCase();
const mimeMap: Record = {
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
heic: "image/heic",
gif: "image/gif",
webp: "image/webp",
};
const fileType = mimeMap[ext || ""] || "image/jpeg"; // default to jpeg

formData.append("image", {
uri: photoUri,
type: fileType,
name: fileName,
} as any); // TypeScript needs this cast

//add device ID to formData
formData.append(
"device_id",
(await getDeviceId()) as unknown as string
);
//add device name to formData
formData.append(
"device_name",
getDeviceInfo().deviceName as unknown as string
);
//add ip address to formData
formData.append("ip_address", ip as unknown as string);
//add longitude to formData
formData.append(
"long_x",
location?.coords.longitude as unknown as string
);
//add latitude to formData
formData.append(
"lat_y",
location?.coords.latitude as unknown as string
);
//add clockin mode to formData
formData.append("clock_mode", "Facial Recognition");
//clockin/clockout
clockinWithFace({ payload: formData });
}, [locationReady, photoUri, location, ip, clockinWithFace]);

const onActionBtnPress = useCallback(() => {
if (!hasPermission) {
requestCameraPermission();
} else if (!photoUri) {
takePicture({ base64: false });
} else {
handleClockin();
}
}, [
handleClockin,
hasPermission,
photoUri,
requestCameraPermission,
takePicture,
]);

return (

{/* Modal */}
setIsVisible(false)}
backdropOpacity={0.9}
backdropColor="#000"
useNativeDriver
statusBarTranslucent
avoidKeyboard={true}
propagateSwipe={true}
deviceHeight={
height +
(Platform.OS === "android"
? StatusBar.currentHeight ?? 0
: 0)
}
>






{!hasPermission ? (



) : photoUri ? (



) : (

setIsReady(true)}
flash="off"
mirror={false}
/>

)}
{hasPermission && (

{photoUri
? "Great! Face capture complete."
: "Keep your face inside the circle and look straight"}

)}
{photoUri && (



Capture Done


)}




{!hasPermission
? "Grant Permission"
: photoUri
? "Clock-In / Clock-Out"
: "Capture"}

{clockingInWithFace && (

)}






);
};

export default React.memo(FaceIDClockinModal);
`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.