Multi Scan
You can utilize the `startFoodDetection` API to scan food items through the device Camera.
How can I implement the startFoodDetection
API and retrieve food items?
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();
}, []);
Example
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
useMultiScanning
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,
};
}
MultiScan
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>
);
});
MultiScanResult
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,
},
});
MultiScanResultItem
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: 'row',
alignItems: 'center',
},
itemFoodName: {
flex: 1,
textTransform: 'capitalize',
marginHorizontal: 8,
fontSize: 16,
},
itemIcon: {
height: 60,
width: 60,
},
clearResult: {
position: 'absolute',
top: -9,
backgroundColor: 'blue',
overflow: 'hidden',
padding: 4,
height: 26,
width: 26,
justifyContent: 'center',
borderRadius: 30,
alignItems: 'center',
right: 0,
},
clearResultItem: {
height: 8,
width: 8,
},
});
export default React.memo(MultiScanResultItem);
AlternativeFood
import React, { useState, useEffect } from 'react';
import { FlatList, Pressable, StyleSheet, Text } from 'react-native';
import {
IconSize,
PassioIconView,
type PassioID,
type PassioIDAttributes,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { getAlternateFoodItems, getAttributesForPassioID } from '../utils';
interface AlternativeFoodProps {
passioId: PassioID;
onAlternativeFoodItemChange?: (item: PassioIDAttributes) => void;
}
const AlternativeFood = React.memo(
({ passioId, onAlternativeFoodItemChange }: AlternativeFoodProps) => {
const [alternativeAttributes, setAlternativeAttributes] =
useState<PassioIDAttributes[]>();
const styles = alternativeFoodStyle();
useEffect(() => {
console.log('alternative called ----');
async function init() {
const passioAttributeID = await getAttributesForPassioID(passioId);
if (passioAttributeID != null) {
const list = await getAlternateFoodItems(passioAttributeID);
setAlternativeAttributes(list);
}
}
init();
}, [passioId]);
if (alternativeAttributes?.length === 0) {
return null;
}
return (
<FlatList
horizontal
data={alternativeAttributes}
renderItem={({ item }) => {
return (
<Pressable
onPress={() => onAlternativeFoodItemChange?.(item)}
style={styles.itemContainer}
>
<PassioIconView
style={styles.itemIcon}
config={{
passioID: item.imageName ?? item.passioID,
iconSize: IconSize.PX180,
passioIDEntityType: item.entityType,
}}
/>
<Text style={styles.itemFoodName}>{item.name}</Text>
</Pressable>
);
}}
keyExtractor={(item) => item.passioID.toString()}
/>
);
}
);
const alternativeFoodStyle = () =>
StyleSheet.create({
itemContainer: {
marginHorizontal: 8,
alignItems: 'center',
marginVertical: 16,
flexDirection: 'row',
},
itemFoodName: {
marginHorizontal: 8,
flex: 1,
textTransform: 'capitalize',
fontSize: 12,
},
itemIcon: {
height: 24,
width: 24,
},
});
export default AlternativeFood;
Utils Methods
getAttributesForFoodCandidates
export async function getAttributesForFoodCandidates({
detectedCandidates,
barcodeCandidates,
packagedFoodCode,
}: FoodCandidates): Promise<PassioIDAttributes[]> {
const detectionCandidateAttributes = detectedCandidates?.map(
({ passioID }) => {
return PassioSDK.getAttributesForPassioID(passioID);
}
);
const barcodeCandidatesAttributes = barcodeCandidates?.map(({ barcode }) => {
return PassioSDK.fetchAttributesForBarcode(barcode);
});
const ocrCandidatesAttributes = packagedFoodCode?.map((ocrCode) => {
return PassioSDK.fetchPassioIDAttributesForPackagedFood(ocrCode);
});
const attrs = await Promise.all([
...(barcodeCandidatesAttributes ?? []),
...(ocrCandidatesAttributes ?? []),
...detectionCandidateAttributes,
]);
return attrs.filter(notEmpty);
}
getAlternateFoodItems
export async function getAlternateFoodItems(
passioIDAttributes: PassioIDAttributes
): Promise<PassioIDAttributes[]> {
const childrenAttributes = passioIDAttributes.children.map(
async ({ passioID }) => {
return PassioSDK.getAttributesForPassioID(passioID);
}
);
const siblingAttributes = passioIDAttributes.siblings.map(
async ({ passioID }) => {
return PassioSDK.getAttributesForPassioID(passioID);
}
);
const parentAttributes = passioIDAttributes.parents.map(
async ({ passioID }) => {
return PassioSDK.getAttributesForPassioID(passioID);
}
);
const combinedAttributes = [
...childrenAttributes,
...siblingAttributes,
...parentAttributes,
];
const attrs = await Promise.all(combinedAttributes);
return attrs.filter(notEmpty);
}
getAttributesForPassioID
export async function getAttributesForPassioID(
passioID: PassioID
): Promise<PassioIDAttributes | null> {
return await PassioSDK.getAttributesForPassioID(passioID);
}
Yeah. Added Multi Scan.
Result
Last updated