[PM-39361] fix: Restart card scanner after camera interruption or reopen#2847
[PM-39361] fix: Restart card scanner after camera interruption or reopen#2847fedemkr wants to merge 1 commit into
Conversation
🤖 Bitwarden Claude Code ReviewOverall Assessment: REQUEST CHANGES Reviewed the card scanner restart logic in Code Review Details
|
| .onChange(of: scenePhase) { newPhase in | ||
| if newPhase == .active, isScanning { | ||
| restartScanning() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // MARK: Private | ||
|
|
||
| /// Stops scanning, waits 300 ms for the camera to fully release, then restarts. | ||
| /// After two retries the sheet is dismissed so the user is never left with a blank screen. | ||
| private func restartScanning() { | ||
| guard scannerRetryCount < 2 else { | ||
| dismiss() | ||
| return | ||
| } | ||
| scannerRetryCount += 1 | ||
| isScanning = false | ||
| Task { @MainActor in | ||
| try? await Task.sleep(for: .milliseconds(300)) | ||
| isScanning = true | ||
| } | ||
| } |
There was a problem hiding this comment.
Details and fix
scannerRetryCount is shared between two very different triggers: genuine scanner failures (onScannerUnavailable) and routine scenePhase == .active transitions (line 66). It only resets on .onAppear, which does not re-fire when the app is backgrounded and foregrounded while the sheet stays presented.
Trace:
- background → foreground Dependency Dashboard #1 →
restartScanning()→scannerRetryCount = 1 - background → foreground re-open Update bitwarden/gh-actions digest to 67ab95d #2 →
scannerRetryCount = 2 - background → foreground Update dependency prettier to v3 #3 → guard fails →
dismiss()
After two normal app-switches the sheet closes on the next foreground even though the camera is fine. A successful restart also never decrements the counter, so each recovery permanently shrinks the budget available for a later real failure.
Consider resetting scannerRetryCount = 0 on a successful restart (e.g. after isScanning = true settles), or only counting onScannerUnavailable-driven restarts toward the cap rather than scene-phase restarts.
| do { | ||
| try uiViewController.startScanning() | ||
| } catch { | ||
| DispatchQueue.main.async { context.coordinator.onScannerUnavailable?() } |
There was a problem hiding this comment.
❓ Is the dispatch here just to avoid updating the view in the middle of a view update?
| .onChange(of: scenePhase) { newPhase in | ||
| if newPhase == .active, isScanning { | ||
| restartScanning() |
There was a problem hiding this comment.
🤔 Does the change of scenePhase trigger for you? I think the scene phase might only work if you're using SwiftUI navigation, not UIKit. You might need to observe the notification center notifications instead.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-39361
📔 Objective
Fixes a blank camera screen when opening the card scanner after the Add Card view was closed while the scanner was still open, or after the device locks and unlocks while the app is on the Add Card screen.
Changes in
CardScannerView.swift:try? startScanning()with explicit error handling; on failure a newonScannerUnavailablecallback is fired (dispatched after the current SwiftUIupdate pass to avoid re-entrant state mutations).
dataScanner(_:becameUnavailableWithError:)in theCoordinatortoforward runtime camera-interruption events through the same callback.
@Environment(\.scenePhase)observation inCardScannerWrapperView; whenthe scene becomes
.activewhile scanning is supposed to be running, astop-then-restart cycle is triggered.
the user is never left with a permanently blank screen.