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?

/**
     * 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 illustration, we establish the useQuickScan hook, which furnishes methods and data such as food, and use of DetectionCameraVieimport 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