Ngày nọ, sếp giao cho mình nhiệm vụ phát triển một tính năng thanh toán qua QR code. Ngoài việc quét QR bằng camera, nhiều người dùng cũng muốn có thể quét QR từ hình ảnh trong thư viện ảnh của họ. Tính năng này đã trở nên quen thuộc trong các ứng dụng ví điện tử và ngân hàng hiện nay.
Việc quét bằng camera dễ thực hiện nhờ thư viện react-native-vision-camera
, nhưng quét QR từ ảnh lại không được hỗ trợ. Sau khi tìm kiếm thư viện, mình thấy phần lớn đã không còn được bảo trì. Vì vậy, mình quyết định tự viết code để dễ dàng kiểm soát và bảo trì dự án hơn.
Bài viết này sẽ hướng dẫn bạn cách implement tính năng quét QR từ ảnh trong một dự án React Native có sẵn hoặc bạn có thể tạo một dự án mới để thực hành theo.
Chọn Thư Viện Phù Hợp Cho Android Và iOS
Trong thư viện vision-camera
, tác giả sử dụng Google MLKit
cho Android và AVCaptureMetadataOutput
cho iOS. Mặc dù đầu tiên mình dự định dùng Google MLKit
cho cả hai nền tảng, nhưng sau khi nghiên cứu, mình nhận ra iOS đã có một thư viện tích hợp là Vision nên mình sẽ sử dụng nó để giảm kích thước ứng dụng.
Ngôn ngữ sẽ được viết bằng Kotlin cho Android và Swift cho iOS.
Thiết Lập Module Native Trong Ứng Dụng Có Sẵn
Theo tài liệu chính thức của React Native, có hai cách để thiết lập native module. Tuy nhiên, mình khuyên bạn nên sử dụng công cụ create-react-native-library
. Công cụ này sẽ giúp bạn tạo ra một package riêng nâng cao khả năng quản lý mã nguồn.
Mở terminal tại thư mục gốc của dự án và chạy lệnh sau:
npx create-react-native-library@latest <tên package>
Ví dụ, nếu mình muốn đặt tên package là qr-code-image-scan
, lệnh sẽ là:
npx create-react-native-library@latest qr-code-image-scan
Sau khi hoàn thành, bạn sẽ thấy một thư mục mới modules
trong dự án của mình. Khi chạy ứng dụng, nếu bạn thấy mã ví dụ có hàm multiply
được import và sử dụng thành công, điều đó nghĩa là bạn đã thiết lập native module thành công.
Giao Diện Người Dùng (JS/TS)
Bạn cần viết một phương thức trong JavaScript/TypeScript để giao tiếp giữa mã native và mã JavaScript, hàm quét QR sẽ nhận một đường dẫn hình ảnh và trả về danh sách các mã QR sau khi được nhận diện.
Mở file modules/react-native-qr-code-image-scan/index.tsx
và thêm hàm sau:
typescript
export function scanFromPath(path: string): Promise<string[]> {
return QrCodeImageScan.scanFromPath(path);
}
Viết Code Quét QR Từ Hình Ảnh Trong Native
Android
Mở thư mục android
của project thông qua Android Studio để dễ dàng sử dụng code completion và thiết lập gradle khi thêm MLKit
.
Vào react-native-qr-code-image-scan/android/build.gradle
và thêm dòng sau vào phần dependencies:
implementation 'com.google.mlkit:barcode-scanning:17.3.0'
Sau đó phân tích lại Gradle.
Trong file react-native-qr-code-image-scan/android/android/QrCodeImageScanModule
, bạn sẽ viết mã native cho Android. Tạo hàm cho việc quét QR code từ hình ảnh như sau:
kotlin
@ReactMethod
fun scanFromPath(path: String, promise: Promise) {
// 1
val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
// 2
val rPath = path.replace("file:","")
val imgFile = File(rPath)
if (!imgFile.exists()) {
promise.reject("", "cannot get image from path: $path")
return
}
// 3
val bitmap = BitmapFactory.decodeFile(imgFile.absolutePath)
val image = InputImage.fromBitmap(bitmap, 0)
// 4
val scanner = BarcodeScanning.getClient(options)
scanner.process(image)
.addOnSuccessListener { barcodes ->
val codes = barcodes.map { it.displayValue }
val arr = Arguments.fromList(codes)
promise.resolve(arr)
}
.addOnFailureListener {
promise.reject("", it.localizedMessage, it)
}
}
iOS
Mở thư mục ios
của project bằng XCode và tìm đến file RNScanQRFromImage.xcworkspace
. Tại thư mục Pods/Development Pods
, tìm file QrCodeImageScan.swift
và thêm đoạn mã sau:
swift
@objc(scanFromPath:withResolver:withRejecter:)
func scanFromPath(path: String, resolve:@escaping RCTPromiseResolveBlock,reject:@escaping RCTPromiseRejectBlock) -> Void {
// 1
guard let url = URL(string: path),
let data = try? Data(contentsOf: url),
let image = UIImage(data: data) else {
reject("", "Cannot get image from path: \(path)", nil)
return
}
guard let cgImage = image.cgImage else {
reject("", "Cannot get cgImage from image", nil)
return
}
// 2
let request = VNDetectBarcodesRequest { request, error in
guard let results = request.results as? [VNBarcodeObservation], error == nil else {
reject("", "Cannot get result from VNDetectBarcodesRequest", nil)
return
}
let qrCodes = results.compactMap { $0.payloadStringValue }
resolve(qrCodes)
}
request.symbologies = [.qr]
// 3
#if targetEnvironment(simulator)
request.revision = VNDetectBarcodesRequestRevision1
#endif
// 4
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {
try handler.perform([request])
} catch {
reject("", "Error when perform request on VNImageRequestHandler: \(error.localizedDescription)", nil)
}
}
Kiểm Tra Tính Năng
Sau khi hoàn tất phát triển, bạn chỉ cần lấy một đường dẫn hình ảnh từ thiết bị và truyền vào phương thức scanFromPath
. Để thực hiện điều này, bạn có thể sử dụng thư viện react-native-image-picker
như sau:
javascript
import { launchImageLibrary, type ImageLibraryOptions } from "react-native-image-picker";
import { scanFromPath } from "react-native-qr-code-image-scan";
const onPress = useCallback(async () => {
const option: ImageLibraryOptions = {
mediaType: "photo",
};
const result = await launchImageLibrary(option);
const uri = result?.assets?.[0]?.uri;
if (!uri) {
return;
}
const codes = await scanFromPath(uri);
setQrCodes(codes);
}, []);
Nếu bạn tạo dự án mẫu, code trong App.tsx
có thể trông như sau:
javascript
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";
import { useState, useCallback } from "react";
import { launchImageLibrary, type ImageLibraryOptions } from "react-native-image-picker";
import { scanFromPath } from "react-native-qr-code-image-scan";
export default function App() {
const [qrCodes, setQrCodes] = useState<string[]>([]);
const onPress = useCallback(async () => {
const option: ImageLibraryOptions = {
mediaType: "photo",
};
const result = await launchImageLibrary(option);
const uri = result?.assets?.[0]?.uri;
if (!uri) {
return;
}
const codes = await scanFromPath(uri);
setQrCodes(codes);
}, []);
return (
<View style={styles.container}>
<StatusBar style="dark" />
<View style={styles.container}>
<Text>Result: {qrCodes}</Text>
<Button onPress={onPress} title="Open Picker" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
Khi chạy ứng dụng, bạn sẽ thấy kết quả trả về từ mã QR trên giao diện người dùng.
Tham Khảo
Nếu bạn muốn tìm hiểu thêm, có thể tham khảo dự án mẫu mà mình đã tạo tại đây: RNScanQRFromImage. Hoặc bạn cũng có thể sử dụng thư viện mình đã phát triển: rn-qr-barcode-image-scan để tiết kiệm thời gian.
source: viblo