Hello, Dev Community!
Today, I want to share a story that’s about more than just writing code. It's about solving a problem, relentless learning, and finally, bringing an idea from a local machine to the hands of thousands of users. This is the journey of building ScanQR—my first major mobile application, now live on the Google Play Store.
This isn't just a "look what I made" post. This is a detailed map of how a modern, high-performance app is born, from the core business logic to the actual code snippets that make it all work.
📲 Download ScanQR for Free on Google Play and See It in Action!
Part 1: The Problem - Why Build Another QR Scanner?
The market is flooded with scanner apps, but I always felt something was missing. Most apps fell into one of these traps:
Slow and Ad-Ridden: The user experience was constantly interrupted.
Too Simple: They just scanned links, with no intelligent features.
Uninspired: Lacked any creative features, like a personalized QR code generator.
I wanted an all-in-one tool: lightning-fast, intelligent, secure, and one that empowers user creativity. That was the vision that led to ScanQR.
Part 2: The Heart of ScanQR - A Deep Dive into Core Features and Code
Let's dissect the features that make ScanQR stand out and look at the exact React Native code that brought them to life.
1. The High-Speed, Performance-Optimized Scanner Engine
This is the most critical feature. A slow scanner is a useless scanner.
The User Experience: Instantly scan codes, even in low light or when they're slightly damaged.
Inside the Code: I used react-native-vision-camera with its useCodeScanner hook. But I didn't just call the function; I heavily optimized it to prevent re-scans and reduce CPU load.
Here’s the core logic:
// ScanScreen.tsx
const lastValueRef = useRef<string>('');
const frameCountRef = useRef(0);
const debounceUntilRef = useRef(0);
const codeScanner = useCodeScanner({
codeTypes: Platform.OS === 'ios' ? CODE_TYPES_IOS : CODE_TYPES_ANDROID,
onCodeScanned: codes => {
// Only process every Nth frame to save battery
frameCountRef.current =
(frameCountRef.current + 1) % SCAN_EVERY_NTH_FRAME;
if (frameCountRef.current !== 0) return;
// "Debounce" Logic: Block rapid, repetitive scans
const now = Date.now();
if (now < debounceUntilRef.current) return;
const raw = codes[0]?.value?.trim();
if (!raw) return;
// If the code is identical to the last one, wait a bit
if (raw === lastValueRef.current) {
debounceUntilRef.current = now + 1000; // Wait 1 second
return;
}
lastValueRef.current = raw;
debounceUntilRef.current = now + 1200; // Set a cooldown for the new code
// Parse and display the result
const parsed = parseLink(raw);
setResult(parsed);
bottomSheetRef.current?.expand();
haptic(); // Trigger haptic feedback
},
});
Dev Analysis:
frameCountRef
: A simple but effective trick to skip frames. This significantly reduces CPU usage and improves battery life.debounceUntilRef
&lastValueRef
: This is a manual debouncing technique. It prevents the app from processing the same QR code dozens of times a second, which would cause the UI to stutter and create a jarring experience.
2. Smart, Context-Aware Actions
ScanQR doesn't just show you a string of text. It understands the content and suggests the appropriate action.
The User Experience: Scan a URL, get an "Open" button. Scan a phone number, get a "Call" button. Scan Wi-Fi credentials, get a "Copy Password" action.
Inside the Code: This logic lives in the primaryAction function. It's a large switch statement that operates on the result from my custom parseLink function.
Here's a snippet:
// ScanScreen.tsx
const primaryAction = async () => {
if (!result) return;
switch (result.kind) {
case 'URL':
await Linking.openURL(result.href);
break;
case 'PHONE':
await Linking.openURL(`tel:${result.number}`);
break;
case 'EMAIL': {
// ... logic to build email query params ...
await Linking.openURL(`mailto:${result.to ?? ''}?${qp.toString()}`);
break;
}
case 'WIFI':
Clipboard.setString(
`WIFI:T:${result.encryption};S:${result.ssid};P:${result.password};;`,
);
Toast.show({
type: 'success',
text1: t('message.copied_to_clipboard'),
});
break;
// ... many more cases for vCard, SMS, Geo, Crypto ...
default:
if ('text' in result) {
Clipboard.setString(result.text);
Toast.show({ /* ... */ });
}
break;
}
};
Dev Analysis: Separating the parsing logic (parseLink
) from the action logic (primaryAction
) keeps the code clean and maintainable. Using React Native's built-in Linking
and Clipboard
APIs allows for deep integration with the operating system.
3. Scan QR Codes from the Image Gallery
This is a convenient feature that many users requested.
The User Experience: Pick a photo containing a QR code from your gallery, and ScanQR will instantly recognize it.
Inside the Code: I combined two powerful libraries:
react-native-image-crop-picker
for selecting the image and@react-native-ml-kit/barcode-scanning
for analyzing the static image.
The implementation is quite straightforward:
// ScanScreen.tsx
import ImageCropPicker from 'react-native-image-crop-picker';
import BarcodeScanning from '@react-native-ml-kit/barcode-scanning';
const onImagePicker = () => {
ImageCropPicker.openPicker({ mediaType: 'photo' })
.then(async image => {
if (!image?.sourceURL && !image?.path) return;
try {
const uri = image.sourceURL ?? image.path;
// Use ML Kit to scan the code from the image's URI
const barcodes = await BarcodeScanning.scan(uri);
const raw = barcodes?.[0]?.value?.trim();
if (!raw) throw new Error('No barcode found');
// Reuse the same parsing and display logic
const parsed = parseLink(raw);
setResult(parsed);
bottomSheetRef.current?.expand();
haptic();
} catch {
Toast.show({ type: 'error', text1: t('message.invalid_qr_code') });
}
})
.catch(err => { console.log('Image picker error', err); });
};
Dev Analysis: The beauty here is reusability. Once I get the raw data from the image, I pass it through the exact same processing flow (parseLink
, setResult
, expand bottom sheet) as a camera scan. This is efficient and reduces code duplication.
Part 3: The Final Stretch - Publishing to the Google Play Store
Finishing the code is only half the battle. The publishing process is a whole other challenge:
- Creating the App Bundle (.aab): Learning the process of signing and generating a production-ready build for Google Play.
- Crafting the Store Presence: Designing an icon, taking screenshots, and writing compelling, SEO-friendly descriptions for the app page.
- Privacy Policy: This is a must. I created a simple, clear privacy policy on GitHub Pages, committing to user privacy.
- The Anxious Wait: Submitting the app and waiting for Google's review. The feeling of seeing that "Your app is now live" email is indescribable!
Conclusion and Your Invitation
The journey of building ScanQR has taught me so much about React Native, performance optimization, and the full product development lifecycle.
It was built with a developer's passion but designed for everyone. If you're looking for a powerful, privacy-respecting, and completely free QR code tool, please give ScanQR a try.
- Download the App: Link
Put It to the Test: Scan everything, generate your own codes, and experience the difference.
Leave Feedback: Your reviews and suggestions are invaluable and will help shape the future of the app.
Thank you for reading my story. I hope it inspires anyone out there with a project idea of their own!
A question for my fellow devs: What was the biggest technical hurdle you've ever faced in a personal project? Share it in the comments below!
Top comments (0)