You can utilize the `startFoodDetection` API to scan food items through the device Camera.
startFoodDetection
API and retrieve food items?/**
* Begin food detection using the device's camera.
* @param options - An object to determine which types of scanning should be performed.
* @param callback - A callback to repeatedly receive food detection events as they occur.
* @returns A `Subscription` that should be retained by the caller while food detection is running. Call `remove` on the subscription to terminate food detection.
*/
useEffect(() => {
// Configuration for food detection
const config: FoodDetectionConfig = {
detectBarcodes: true,
detectPackagedFood: true,
detectNutritionFacts: false,
};
// Start food detection and subscribe to events
const subscription = PassioSDK.startFoodDetection(
config,
async (detection: FoodDetectionEvent) => {
const { candidates } = detection;
// Check if there are candidates with relevant information
if (
candidates &&
((candidates.detectedCandidates &&
candidates.detectedCandidates.length > 0) ||
(candidates.packagedFoodCode &&
candidates.packagedFoodCode.length > 0) ||
(candidates.barcodeCandidates &&
candidates.barcodeCandidates.length > 0))
) {
// Get attributes for the detected food candidates
// This method is mentioned below in utility method
const attributes = await getAttributesForFoodCandidates(candidates);
// Update passioIDAttributes state
setPassioIDAttributes((food) => {
const copyOfPassioIDAttributes: PassioIDAttributes[] = [];
// Filter out duplicate entries
attributes.forEach((value) => {
if (food.every((item) => item.passioID !== value.passioID)) {
copyOfPassioIDAttributes.push(value);
}
});
// If there are new attributes, update the state
if (copyOfPassioIDAttributes.length > 0) {
return [...food, ...copyOfPassioIDAttributes];
} else {
return food;
}
});
setIsLoading(false);
}
}
);
// Cleanup function to unsubscribe when the component unmounts
return () => subscription.remove();
}, []);
Require @gorhom/bottom-sheet
for bottom sheet
In this , we establish the useMultiscan
hook, which furnishes methods and data such as food
, and use of DetectionCameraView
import {
type FoodDetectionConfig,
type FoodDetectionEvent,
type PassioIDAttributes,
PassioSDK,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { getAttributesForFoodCandidates } from '../../utils';
import { useEffect, useState, useCallback } from 'react';
/**
* Custom hook for handling multi-scanning using PassioSDK.
* It provides functions and state variables related to the detection of multiple food items.
*/
export function useMultiScanning() {
// State variables
const [isLoading, setIsLoading] = useState(true);
const [passioIDAttributes, setPassioIDAttributes] = useState<
PassioIDAttributes[]
>([]);
useEffect(() => {
// Configuration for food detection
const config: FoodDetectionConfig = {
detectBarcodes: true,
detectPackagedFood: true,
detectNutritionFacts: false,
};
// Start food detection and subscribe to events
const subscription = PassioSDK.startFoodDetection(
config,
async (detection: FoodDetectionEvent) => {
const { candidates } = detection;
// Check if there are candidates with relevant information
if (
candidates &&
((candidates.detectedCandidates &&
candidates.detectedCandidates.length > 0) ||
(candidates.packagedFoodCode &&
candidates.packagedFoodCode.length > 0) ||
(candidates.barcodeCandidates &&
candidates.barcodeCandidates.length > 0))
) {
// Get attributes for the detected food candidates
// This method is mentioned below in utility method
const attributes = await getAttributesForFoodCandidates(candidates);
// Update passioIDAttributes state
setPassioIDAttributes((food) => {
const copyOfPassioIDAttributes: PassioIDAttributes[] = [];
// Filter out duplicate entries
attributes.forEach((value) => {
if (food.every((item) => item.passioID !== value.passioID)) {
copyOfPassioIDAttributes.push(value);
}
});
// If there are new attributes, update the state
if (copyOfPassioIDAttributes.length > 0) {
return [...food, ...copyOfPassioIDAttributes];
} else {
return food;
}
});
setIsLoading(false);
}
}
);
// Cleanup function to unsubscribe when the component unmounts
return () => subscription.remove();
}, []);
// Function to delete a food item by its passioID
const onDeleteFoodItem = useCallback((passioID: string) => {
setPassioIDAttributes((prev) =>
prev.filter((value) => value.passioID !== passioID)
);
}, []);
// Return the hook's public API
return {
isLoading,
passioIDAttributes,
onDeleteFoodItem,
};
}
import { ActivityIndicator, View } from 'react-native';
import {
blackBackgroundStyle,
bottomSheet,
cameraStyle,
} from './MultiScanningStyle';
import BottomSheet from '@gorhom/bottom-sheet';
import { DetectionCameraView } from '@passiolife/nutritionai-react-native-sdk-v2';
import React, { useMemo, useRef } from 'react';
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
import { useMultiScanning } from './hooks/useMultiScanning';
import { MultiScanResult } from './MultiScanResult';
export const MultiScan = gestureHandlerRootHOC(() => {
const { isLoading, passioIDAttributes, onDeleteFoodItem } =
useMultiScanning();
const bottomSheetModalRef = useRef<BottomSheet>(null);
const snapPoints = useMemo(() => ['20%', '75%'], []);
return (
<View style={blackBackgroundStyle}>
<DetectionCameraView style={cameraStyle} />
<BottomSheet
ref={bottomSheetModalRef}
index={0}
snapPoints={snapPoints}
backgroundStyle={bottomSheet.backgroundStyle}
enableContentPanningGesture={
isLoading || passioIDAttributes.length <= 3
}
>
{isLoading || passioIDAttributes.length === 0 ? (
<ActivityIndicator />
) : (
<View style={[bottomSheet.container]}>
<MultiScanResult
attributes={passioIDAttributes}
onClearResultPress={onDeleteFoodItem}
/>
</View>
)}
</BottomSheet>
</View>
);
});
import { StyleSheet } from 'react-native';
import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { type PassioIDAttributes } from '@passiolife/nutritionai-react-native-sdk-v2';
import React from 'react';
import { MultiScanResultItem } from './MultiScanResultItem';
export interface MultiScanResultProps {
attributes: PassioIDAttributes[];
onClearResultPress: (item: PassioIDAttributes) => void;
}
export const MultiScanResult = ({
attributes,
onClearResultPress,
}: MultiScanResultProps) => {
const styles = multiScanResultStyle();
const renderItem = ({ item }: { item: PassioIDAttributes }) => {
return (
<MultiScanResultItem
attribute={item}
onClearResultPress={onClearResultPress}
/>
);
};
return (
<BottomSheetFlatList
contentContainerStyle={styles.flatList}
data={attributes}
renderItem={renderItem}
keyExtractor={(item) => item.passioID}
extraData={attributes}
/>
);
};
const multiScanResultStyle = () =>
StyleSheet.create({
flatList: {
marginVertical: 8,
paddingBottom: 250,
},
});
import React from 'react';
import { Image, Pressable, StyleSheet, Text, View } from 'react-native';
import {
IconSize,
PassioIconView,
type PassioIDAttributes,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import AlternativeFood from '../quick/AlternativeFood';
export interface MultiScanResultItemProps {
attribute: PassioIDAttributes;
onClearResultPress?: (item: PassioIDAttributes) => void;
}
const MultiScanResultItem = ({
attribute,
onClearResultPress,
}: MultiScanResultItemProps) => {
const styles = quickScanStyle();
const onAlternativeFoodItemChange = () => {};
return (
<View style={styles.itemContainer}>
<View style={styles.foodResult}>
<PassioIconView
style={styles.itemIcon}
config={{
passioID: attribute.imageName ?? attribute.passioID,
iconSize: IconSize.PX180,
passioIDEntityType: attribute.entityType,
}}
/>
<Text style={styles.itemFoodName}>{attribute.name}</Text>
</View>
<AlternativeFood
onAlternativeFoodItemChange={onAlternativeFoodItemChange}
passioId={attribute.passioID}
/>
<Pressable
style={styles.clearResult}
onPress={() => onClearResultPress?.(attribute)}
>
<Image
resizeMode="contain"
style={styles.clearResultItem}
source={require('../../../assets/ic_close_white.png')}
/>
</Pressable>
</View>
);
};
const quickScanStyle = () =>
StyleSheet.create({
itemContainer: {
padding: 12,
marginHorizontal: 16,
backgroundColor: 'white',
marginTop: 16,
left: 0,
},
foodResult: {
flexDirection: