Quick 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 through quick scan?
startFoodDetection
API and retrieve food items through quick scan?/**
* 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.
*/
// Configuration for food detection
useEffect(() => {
const config: FoodDetectionConfig = {
detectBarcodes: true,
detectPackagedFood: true,
detectNutritionFacts: true,
};
// Start food detection and subscribe to events
const subscription = PassioSDK.startFoodDetection(
config,
handleFoodDetection
);
// Cleanup function to unsubscribe when the component unmounts
return () => subscription.remove();
}, []); // Empty dependency array to run the effect only once during component mount
Example
In this , we establish the useQuickScan
hook, which furnishes methods and data such as food
, and use of DetectionCameraVie
import React from 'react';
useQuickScan
import { useEffect, useRef, useState, useCallback } from 'react';
import {
PassioSDK,
type PassioIDAttributes,
type FoodDetectionConfig,
type NutritionFacts,
type FoodDetectionEvent,
} from '@passiolife/nutritionai-react-native-sdk-v2';
import { getAlternateFoodItems } from '../utils';
/**
* Custom hook for handling quick food scanning using PassioSDK.
* It provides functions and state variables related to food detection and alternative food items.
*/
export const useQuickScan = () => {
// State variables
const [passioIDAttributes, setPassioIDAttributes] =
useState<PassioIDAttributes | null>(null);
const [alternative, setAlternativePassioIDAttributes] = useState<
PassioIDAttributes[] | null
>(null);
const [loading, setLoading] = useState(true);
const passioIDAttributesRef = useRef<PassioIDAttributes | null>(null);
const [nutritionFacts, setNutritionFacts] = useState<
NutritionFacts | undefined
>(undefined);
// Function to clear the scanning results
const onClearResultPress = () => {
setLoading(true);
passioIDAttributesRef.current = null;
setPassioIDAttributes(null);
setAlternativePassioIDAttributes(null);
};
useEffect(() => {
// Function to handle food detection events
const handleFoodDetection = async (detection: FoodDetectionEvent) => {
const { candidates , nutritionFacts} = detection;
if (
nutritionFacts !== undefined &&
nutritionFacts.servingSizeGram !== undefined
) {
setNutritionFacts(detection.nutritionFacts);
return;
}
if (!candidates) {
return;
}
let attributes: PassioIDAttributes | null = null;
// Determine the type of food detection and fetch attributes accordingly
if (candidates.barcodeCandidates?.[0]) {
const barcode = candidates.barcodeCandidates[0].barcode;
attributes = await PassioSDK.fetchAttributesForBarcode(barcode);
} else if (candidates.packagedFoodCode?.[0]) {
const packagedFoodCode = candidates.packagedFoodCode[0];
attributes =
await PassioSDK.fetchPassioIDAttributesForPackagedFood(
packagedFoodCode
);
} else if (candidates.detectedCandidates?.[0]) {
const passioID = candidates.detectedCandidates[0].passioID;
attributes = await PassioSDK.getAttributesForPassioID(passioID);
}
if (attributes === null) {
return;
}
// Check if the detected food is different from the previous one
if (attributes?.passioID !== passioIDAttributesRef.current?.passioID) {
passioIDAttributesRef.current = attributes;
// Update state variables and fetch alternative food items
setPassioIDAttributes((prev) => {
if (attributes?.passioID === prev?.passioID) {
return prev;
} else {
async function callAlternative() {
if (attributes) {
const alternativeItems =
await getAlternateFoodItems(attributes);
setAlternativePassioIDAttributes(alternativeItems);
}
}
callAlternative();
return attributes;
}
});
setLoading(false);
}
};
// Configuration for food detection
const config: FoodDetectionConfig = {
detectBarcodes: true,
detectPackagedFood: true,
detectNutritionFacts: true,
};
// Start food detection and subscribe to events
const subscription = PassioSDK.startFoodDetection(
config,
handleFoodDetection
);
// Cleanup function to unsubscribe when the component unmounts
return () => subscription.remove();
}, []); // Empty dependency array to run the effect only once during component mount
// Function to handle changes in alternative food items
const onAlternativeFoodItemChange = useCallback(
async (attribute: PassioIDAttributes) => {
const alternativeItems = await getAlternateFoodItems(attribute);
setAlternativePassioIDAttributes(alternativeItems);
setPassioIDAttributes(attribute);
passioIDAttributesRef.current = attribute;
},
[]
);
// Return the hook's public API
return {
loading,
passioIDAttributes,
onAlternativeFoodItemChange,
onClearResultPress,
alternative,
nutritionFacts
};
};
QuickScanningScreen
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { DetectionCameraView } from '@passiolife/nutritionai-react-native-sdk-v2';
import { blackBackgroundStyle } from './QuickScanningScreen.Style';
import { useQuickScan } from './useQuickScan';
import { QuickFoodResult } from './views/QuickFoodResult';
export const QuickScanningScreen = () => {
const {
loading,
passioIDAttributes,
onClearResultPress,
alternative,
onAlternativeFoodItemChange,
} = useQuickScan();
const styles = quickScanStyle();
return (
<View style={blackBackgroundStyle}>
<DetectionCameraView style={styles.detectionCamera} />
{loading ? <ActivityIndicator style={styles.loadingIndicator} /> : null}
{passioIDAttributes !== null ? (
<QuickFoodResult
attribute={passioIDAttributes}
onAlternativeFoodItemChange={onAlternativeFoodItemChange}
onClearResultPress={onClearResultPress}
alternativeAttributes={alternative}
/>
) : null}
</View>
);
};
const quickScanStyle = () =>
StyleSheet.create({
detectionCamera: {
flex: 1,
width: '100%',
},
loadingIndicator: {
backgroundColor: 'white',
minHeight: 100,
marginVertical: 30,
position: 'absolute',
bottom: 0,
right: 16,
left: 16,
},
});
QuickFoodResult
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 '../AlternativeFood';
export interface QuickFoodResultProps {
attribute: PassioIDAttributes;
alternativeAttributes?: PassioIDAttributes[] | null;
onAlternativeFoodItemChange?: (item: PassioIDAttributes) => void;
onClearResultPress?: () => void;
}
export const QuickFoodResult = ({
attribute,
alternativeAttributes,
onAlternativeFoodItemChange,
onClearResultPress,
}: QuickFoodResultProps) => {
const styles = quickFoodResultStyle();
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>
{alternativeAttributes && (
<AlternativeFood
alternative={alternativeAttributes}
onAlternativeFoodItemChange={onAlternativeFoodItemChange}
/>
)}
<Pressable style={styles.clearResult} onPress={onClearResultPress}>
<Image
resizeMode="contain"
source={require('../../../../assets/ic_close_white.png')}
/>
</Pressable>
</View>
);
};
const quickFoodResultStyle = () =>
StyleSheet.create({
itemContainer: {
position: 'absolute',
bottom: 0,
right: 0,
padding: 12,
marginHorizontal: 16,
backgroundColor: 'white',
marginVertical: 30,
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: -30,
right: 0,
},
});
AlternativeFood
import React, { useCallback } from 'react';
import { FlatList, Pressable, StyleSheet, Text } from 'react-native';
import {
IconSize,
PassioIconView,
type PassioIDAttributes,
} from '@passiolife/nutritionai-react-native-sdk-v2';
interface AlternativeFoodProps {
alternative: PassioIDAttributes[];
onAlternativeFoodItemChange?: (item: PassioIDAttributes) => void;
}
const AlternativeFood = ({
alternative,
onAlternativeFoodItemChange,
}: AlternativeFoodProps) => {
const styles = alternativeFoodStyle();
const renderItem = useCallback(
({ item }: { item: PassioIDAttributes }) => (
<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>
),
[onAlternativeFoodItemChange, styles]
);
return (
<FlatList
horizontal
data={alternative}
renderItem={renderItem}
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;
Utility Methods
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);
}
Yeah. Added Quick Scan.
Result
Last updated