Introduction:
Barcode scanners are essential tools in various applications, from inventory management to mobile shopping. In this blog, we'll explore how to create a barcode scanner app using React Native and the powerful react-native-vision-camera
library.
Scanner.js
// Import necessary modules and components
import { useEffect, useState } from 'react';
import {
Alert,
Image,
Linking,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import {
Camera,
useCameraDevice,
useCameraPermission,
useCodeScanner,
} from 'react-native-vision-camera';
// Import utility functions from 'utils'
import {
formatWifiData,
getCountryOfOriginFromBarcode,
openExternalLink,
} from './utils';
// Define the ScannerScreen component
export function ScannerScreen({ navigation }) {
// State variables
const [torchOn, setTorchOn] = useState(false);
const [enableOnCodeScanned, setEnableOnCodeScanned] = useState(true);
// Camera permission hooks
const {
hasPermission: cameraHasPermission,
requestPermission: requestCameraPermission,
} = useCameraPermission();
// Get the camera device (back camera)
const device = useCameraDevice('back');
// Handle camera permission on component mount
useEffect(() => {
handleCameraPermission();
}, []);
// Use the code scanner hook to configure barcode scanning
const codeScanner = useCodeScanner({
codeTypes: ['qr', 'ean-13'],
onCodeScanned: (codes) => {
// Check if code scanning is enabled
if (enableOnCodeScanned) {
let value = codes[0]?.value;
let type = codes[0]?.type;
console.log(codes[0]);
// Handle QR code
if (type === 'qr') {
openExternalLink(value).catch((error) => {
showAlert('Detail', formatWifiData(value), false);
});
} else {
// Handle other barcode types
const countryOfOrigin = getCountryOfOriginFromBarcode(value);
console.log(`Country of Origin for ${value}: ${countryOfOrigin}`);
showAlert(value, countryOfOrigin);
}
// Disable code scanning to prevent rapid scans
setEnableOnCodeScanned(false);
}
},
});
// Handle camera permission
const handleCameraPermission = async () => {
const granted = await requestCameraPermission();
if (!granted) {
alert(
'Camera permission is required to use the camera. Please grant permission in your device settings.'
);
// Optionally, open device settings using Linking API
Linking.openSettings();
}
};
// Show alert with customizable content
const showAlert = (
value = '',
countryOfOrigin = '',
showMoreBtn = true
) => {
Alert.alert(
value,
countryOfOrigin,
showMoreBtn
? [
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{
text: 'More',
onPress: () => {
setTorchOn(false);
setEnableOnCodeScanned(true);
openExternalLink(
'https://www.barcodelookup.com/' + value
);
},
},
]
: [
{
text: 'Cancel',
onPress: () => setEnableOnCodeScanned(true),
style: 'cancel',
},
],
{ cancelable: false }
);
};
// Round button component with image
const RoundButtonWithImage = () => {
return (
<TouchableOpacity
onPress={() => setTorchOn((prev) => !prev)}
style={styles.buttonContainer}>
<View style={styles.button}>
<Image
source={
torchOn
? require('./assets/flashlight_on.png')
: require('./assets/torch_off.png')
}
style={styles.buttonImage}
/>
</View>
</TouchableOpacity>
);
};
// Render content based on camera device availability
if (device == null)
return (
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<Text style={{ margin: 10 }}>Camera Not Found</Text>
</View>
);
// Return the main component structure
return (
<SafeAreaView style={{ flex: 1 }}>
<RoundButtonWithImage />
<Camera
codeScanner={codeScanner}
style={StyleSheet.absoluteFill}
device={device}
isActive={true}
torch={torchOn ? 'on' : 'off'}
onTouchEnd={() => setEnableOnCodeScanned(true)}
/>
</SafeAreaView>
);
}
// Styles for the component
const styles = StyleSheet.create({
buttonContainer: {
alignItems: 'center',
position: 'absolute',
zIndex: 1,
right: 20,
top: 20,
},
button: {
backgroundColor: '#FFF', // Button background color
borderRadius: 50, // Make it round (half of the width and height)
width: 50,
height: 50,
justifyContent: 'center',
alignItems: 'center',
},
buttonImage: {
width: 25, // Adjust the width and height of the image as needed
height: 25,
},
});
utils.js
// Import Linking from react-native
const { Linking } = require('react-native');
// Function to open an external link
export const openExternalLink = url => {
return Linking.openURL(url)
.then(() => {
console.log('Link opened successfully');
})
.catch(err => {
console.error('Error opening external link:', err);
throw err; // Rethrow the error for the calling code to handle
});
};
// Function to format WiFi data
export const formatWifiData = wifiData => {
// Split the WiFi data into parts and filter out empty parts
const dataParts = wifiData.split(';').filter(part => part.trim() !== '');
// Format each part as key-value pairs and join them with newline
const formattedData = dataParts.map(part => {
const [key, value] = part.split(':');
return `${key}: ${value}`;
});
return formattedData.join('\n');
};
// Function to get the country of origin from a barcode
export const getCountryOfOriginFromBarcode = barcode => {
// Extract the first three digits of the barcode as the prefix
const prefix = parseInt(barcode.substring(0, 3), 10);
// Define ranges for country codes and their corresponding countries
const countryRanges = {
'000-019': 'United States and Canada',
'020-029': 'Restricted distribution',
'030-039': 'United States drugs (National Drug Code)',
'040-049':
'Used to issue restricted circulation numbers within a geographic region',
'050-059': 'Reserved for future use',
'060-099': 'United States and Canada',
'100-139': 'United States',
'200-299': 'Restricted distribution',
'300-379': 'France and Monaco',
380: 'Bulgaria',
383: 'Slovenia',
385: 'Croatia',
387: 'Bosnia and Herzegovina',
389: 'Montenegro',
'400-440': 'Germany',
'450-459': 'Japan',
'460-469': 'Russia',
470: 'Kyrgyzstan',
471: 'Taiwan',
474: 'Estonia',
475: 'Latvia',
476: 'Azerbaijan',
477: 'Lithuania',
478: 'Uzbekistan',
479: 'Sri Lanka',
480: 'Philippines',
481: 'Belarus',
482: 'Ukraine',
484: 'Moldova',
485: 'Armenia',
486: 'Georgia',
487: 'Kazakhstan',
488: 'Tajikistan',
489: 'Hong Kong',
'490-499': 'Japan',
'500-509': 'United Kingdom',
'520-521': 'Greece',
528: 'Lebanon',
529: 'Cyprus',
530: 'Albania',
531: 'Macedonia',
535: 'Malta',
539: 'Ireland',
'540-549': 'Belgium and Luxembourg',
560: 'Portugal',
569: 'Iceland',
'570-579': 'Denmark, Faroe Islands, and Greenland',
590: 'Poland',
594: 'Romania',
599: 'Hungary',
'600-601': 'South Africa',
603: 'Ghana',
604: 'Senegal',
608: 'Bahrain',
609: 'Mauritius',
611: 'Morocco',
613: 'Algeria',
615: 'Nigeria',
616: 'Kenya',
618: 'Côte d’Ivoire',
619: 'Tunisia',
621: 'Syria',
622: 'Egypt',
624: 'Libya',
625: 'Jordan',
626: 'Iran',
627: 'Kuwait',
628: 'Saudi Arabia',
629: 'United Arab Emirates',
'640-649': 'Finland',
'690-695': 'China',
'700-709': 'Norway',
729: 'Israel',
'730-739': 'Sweden',
740: 'Guatemala',
741: 'El Salvador',
742: 'Honduras',
743: 'Nicaragua',
744: 'Costa Rica',
750: 'Mexico',
'754-755': 'Canada',
759: 'Venezuela',
'760-769': 'Switzerland and Liechtenstein',
'770-771': 'Colombia',
773: 'Uruguay',
775: 'Peru',
777: 'Bolivia',
779: 'Argentina',
780: 'Chile',
784: 'Paraguay',
785: 'Peru',
786: 'Ecuador',
'789-790': 'Brazil',
'800-839': 'Italy, San Marino, and Vatican City',
'840-849': 'Spain and Andorra',
850: 'Cuba',
858: 'Slovakia',
859: 'Czech Republic',
860: 'Serbia',
865: 'Mongolia',
867: 'North Korea',
'868-869': 'Turkey',
'870-879': 'Netherlands',
880: 'South Korea',
884: 'Cambodia',
885: 'Thailand',
888: 'Singapore',
890: 'India',
893: 'Vietnam',
896: 'Pakistan',
899: 'Indonesia',
'900-919': 'Austria',
'930-939': 'Australia',
'940-949': 'New Zealand',
955: 'Malaysia',
958: 'Macau',
'960-969': 'GS1 Global Office: GTIN-8 allocations',
977: 'Serial publications (ISSN)',
'978-979': 'Bookland (ISBN)',
980: 'Refund receipts',
'981-984': 'GS1 coupon identification for common currency areas',
'990-999': 'Coupon identification',
};
// Iterate through the country ranges to find the matching range
for (const range in countryRanges) {
const [start, end] = range.split('-').map(Number);
// Check if the prefix falls within the current range
if (prefix >= start && prefix <= end) {
return countryRanges[range];
}
}
// If no matching range is found, return a default message
return 'Country not specified';
};
Conclusion:
Congratulations! You've successfully built a barcode scanner app with React Native and the react-native-vision-camera
library. Feel free to customize and enhance the app based on your specific requirements. Happy coding!
Top comments (1)
Hello. Thanks for your detailed explanation. But I have got a issue while trying with 'react-native-vision-camera' module.
Please help me.
Could not determine the dependencies of task ':react-native-vision-camera:compileDebugAidl'.