Browse Source

refactor(scanner): improve barcode scanner initialization and error handling

- Fix scanner listener initialization in class component
  * Replace Vue ref with direct class property for scanListener
  * Remove unnecessary .value accesses throughout the code

- Enhance error handling and cleanup
  * Add proper cleanup on scanner errors
  * Add cleanup in stopScanning method
  * Improve error message formatting
  * Add error handling in handleScanResult

- Improve state management
  * Move state updates into try/catch blocks
  * Add finally block to reset state after scan processing
  * Better handling of processing states during scanning

- Add comprehensive logging
  * Log each step of scanner initialization
  * Add detailed error logging
  * Format object logs to prevent [object Object] output

- Code style improvements
  * Fix indentation throughout the file
  * Consistent error handling patterns
  * Better code organization and readability
qrcode-capacitor
Matthew Raymer 3 months ago
parent
commit
ff75fa5349
  1. 108
      src/views/ContactQRScanShowView.vue

108
src/views/ContactQRScanShowView.vue

@ -137,7 +137,7 @@ import {
BarcodeScanner,
type ScanResult,
} from "@capacitor-mlkit/barcode-scanning";
import { ref, type Ref, reactive } from "vue";
import { ref, reactive } from "vue";
// Declare global constants
declare const __USE_QR_READER__: boolean;
@ -241,7 +241,7 @@ export default class ContactQRScanShowView extends Vue {
private isCapturingPhoto = false;
private appStateListener?: { remove: () => Promise<void> };
private scanListener: Ref<PluginListenerHandle | null> = ref(null);
private scanListener: PluginListenerHandle | null = null;
private state = reactive<AppState>({
isProcessing: false,
processingStatus: "",
@ -307,7 +307,17 @@ export default class ContactQRScanShowView extends Vue {
this.appStateListener = await App.addListener(
"appStateChange",
(state: AppStateChangeEvent) => {
logger.log("App state changed:", state);
const stateInfo = {
isActive: state.isActive,
timestamp: new Date().toISOString(),
cameraActive: this.cameraActive,
scannerState: {
...this.state.scannerState,
// Convert complex objects to strings to avoid [object Object]
error: this.state.scannerState.error?.toString() || null,
},
};
logger.log("App state changed:", JSON.stringify(stateInfo, null, 2));
if (!state.isActive) {
this.cleanupCamera();
}
@ -316,15 +326,31 @@ export default class ContactQRScanShowView extends Vue {
// Add pause listener
await App.addListener("pause", () => {
logger.log("App paused");
const pauseInfo = {
timestamp: new Date().toISOString(),
cameraActive: this.cameraActive,
scannerState: {
...this.state.scannerState,
error: this.state.scannerState.error?.toString() || null,
},
isProcessing: this.state.isProcessing,
};
logger.log("App paused:", JSON.stringify(pauseInfo, null, 2));
this.cleanupCamera();
});
// Add resume listener
await App.addListener("resume", () => {
logger.log("App resumed");
// Don't automatically reinitialize camera on resume
// Let user explicitly request camera access again
const resumeInfo = {
timestamp: new Date().toISOString(),
cameraActive: this.cameraActive,
scannerState: {
...this.state.scannerState,
error: this.state.scannerState.error?.toString() || null,
},
isProcessing: this.state.isProcessing,
};
logger.log("App resumed:", JSON.stringify(resumeInfo, null, 2));
});
logger.log("App lifecycle listeners setup complete");
@ -436,41 +462,59 @@ export default class ContactQRScanShowView extends Vue {
try {
this.state.isProcessing = true;
this.state.processingStatus = "Starting camera...";
logger.log("Opening mobile camera - starting initialization");
// Check current permission status
const status = await BarcodeScanner.checkPermissions();
logger.log("Camera permission status:", JSON.stringify(status, null, 2));
if (status.camera !== "granted") {
// Request permission if not granted
logger.log("Requesting camera permissions...");
const permissionStatus = await BarcodeScanner.requestPermissions();
if (permissionStatus.camera !== "granted") {
throw new Error("Camera permission not granted");
}
logger.log(
"Camera permission granted:",
JSON.stringify(permissionStatus, null, 2),
);
}
// Set up the listener before starting the scan
// Remove any existing listener first
try {
const listener = await BarcodeScanner.addListener(
if (this.scanListener) {
logger.log("Removing existing barcode listener");
await this.scanListener.remove();
this.scanListener = null;
}
} catch (error) {
logger.error("Error removing existing listener:", error);
// Continue with setup even if removal fails
}
// Set up the listener before starting the scan
logger.log("Setting up new barcode listener");
this.scanListener = await BarcodeScanner.addListener(
"barcodesScanned",
async (result: ScanResult) => {
logger.log(
"Barcode scan result received:",
JSON.stringify(result, null, 2),
);
if (result.barcodes && result.barcodes.length > 0) {
this.state.processingDetails = `Processing QR code: ${result.barcodes[0].rawValue}`;
await this.handleScanResult(result.barcodes[0].rawValue);
}
},
);
// Only set the listener if we successfully got one
if (listener) {
this.scanListener.value = listener;
}
} catch (error) {
logger.error("Error setting up barcode listener:", error);
throw new Error("Failed to initialize barcode scanner");
}
logger.log("Barcode listener setup complete");
// Start the scanner
logger.log("Starting barcode scanner");
await BarcodeScanner.startScan();
logger.log("Barcode scanner started successfully");
this.state.isProcessing = false;
this.state.processingStatus = "";
} catch (error) {
@ -481,19 +525,37 @@ export default class ContactQRScanShowView extends Vue {
this.showError(
error instanceof Error ? error.message : "Failed to open camera",
);
// Cleanup on error
try {
if (this.scanListener) {
await this.scanListener.remove();
this.scanListener = null;
}
} catch (cleanupError) {
logger.error("Error during cleanup:", cleanupError);
}
}
}
private async handleScanResult(rawValue: string) {
try {
this.state.isProcessing = true;
this.state.processingStatus = "Processing QR code...";
this.state.processingDetails = `Scanned value: ${rawValue}`;
try {
// Stop scanning before processing
await this.stopScanning();
// Process the scan result
await this.onScanDetect({ rawValue });
} catch (error) {
logger.error("Error handling scan result:", error);
this.showError("Failed to process scan result");
} finally {
this.state.isProcessing = false;
this.state.processingStatus = "";
this.state.processingDetails = "";
}
}
@ -880,6 +942,13 @@ export default class ContactQRScanShowView extends Vue {
async stopScanning() {
try {
// Remove the listener first
if (this.scanListener) {
await this.scanListener.remove();
this.scanListener = null;
}
// Stop the scanner
await BarcodeScanner.stopScan();
this.state.scannerState.processingStatus = "Scan stopped";
this.state.scannerState.isProcessing = false;
@ -889,6 +958,7 @@ export default class ContactQRScanShowView extends Vue {
error instanceof Error ? error.message : String(error);
this.state.scannerState.error = `Error stopping scan: ${errorMessage}`;
this.state.scannerState.isProcessing = false;
logger.error("Error stopping scanner:", error);
}
}

Loading…
Cancel
Save