My Android barcode scanner had a CameraX bug: after rotating a tablet through 180°, the preview still looked correct, but the frames received by the analyzer were upside down.
The interesting part wasn't the final fix. It was deciding when we actually had enough evidence to trust it.
A coding agent handled most of the debugging: reading the code, adding instrumentation, running adb logcat, changing the implementation, and checking the results. I handled the physical tablet.
The debugging loop was unusual.
The agent started adb logcat. I opened the scanner and physically rotated
the tablet. Then the agent collected the new values and adjusted the
instrumentation.
We repeated this several times until we had the states we needed to compare.
These were the values that eventually explained the bug. Before rotation:
Configuration.orientation=2
displayRotation=1
analysisTargetRotation=1
imageRotation=0
After rotation:
Configuration.orientation=2
displayRotation=3
analysisTargetRotation=1
imageRotation=0
The display had moved on. ImageAnalysis had not.
Getting to that evidence required stopping a different line of work first: waiting for a rotation value to update, then rebinding the camera.
During an earlier experiment, the agent observed that the display-change callback had already fired while previewView.display.rotation still held the old value. About half a second later, that value became correct.
The agent concluded:
post is not enough. We need to wait for previewView.display.rotation to actually change before rebinding.
The proposed direction looked like this:
DisplayListener
→ callback
→ display.rotation still has the old value
→ post
→ still the old value
→ wait
→ get the new rotation
→ rebind CameraX
I stopped the work at that point. I did not yet know the correct fix. We might have needed another listener, different ImageAnalysis settings, or a deeper investigation of YUV processing.
But the timing approach left basic questions unanswered. Why did rotating the tablet require recreating the camera use cases? What exactly were we waiting for? Half a second on this tablet—how long on another?
I asked the agent to stop fixing things and prove the cause first.
We added these values to the diagnostics:
Configuration.orientation
displayRotation
Preview.targetRotation
ImageAnalysis.targetRotation
ImageProxy.rotationDegrees
Then we repeated the physical experiment. The agent started a fresh log capture; I opened the scanner screen and rotated the tablet. It collected the output, adjusted the instrumentation, and asked me to repeat the experiment when needed.
That produced the before-and-after logs at the top of this article.
The tablet had moved from ROTATION_90 to ROTATION_270. Both positions were still landscape, so Configuration.orientation remained 2:
ORIENTATION_LANDSCAPE → ORIENTATION_LANDSCAPE
The display already reported rotation 3, while the analyzer retained its old target:
display ROTATION_270
ImageAnalysis.target ROTATION_90
ImageProxy.rotationDegrees consequently remained 0.
We now had a specific, measured cause: ImageAnalysis.targetRotation was not being updated when the device rotated through 180°. That gave us something concrete to fix and a way to verify the result.
The implementation moved to this flow:
OrientationEventListener
→ calculatedRotation
→ Preview.targetRotation = calculatedRotation
→ ImageAnalysis.targetRotation = calculatedRotation
The existing Preview and ImageAnalysis instances remained in place. Handling the rotation no longer required delay, postDelayed, polling, unbindAll(), or bindToLifecycle().
There was one subtle mapping detail. The degrees reported by OrientationEventListener could not be mapped directly to the Surface.ROTATION_* constant with the same angle in its name. In our case, the range 45..134° had to map to ROTATION_270, not ROTATION_90.
We verified the change with the same physical experiment. Before rotating the tablet:
displayRotation=1
previewTargetRotation=1
analysisTargetRotation=1
imageRotation=0
orientationDegrees=70..79
calculatedRotation=3
displayRotation=3
previewTargetRotation=3
analysisTargetRotation=3
imageRotation=180
The chain was consistent. CameraX received the new targetRotation, and ImageProxy.rotationDegrees changed from 0 to 180, without recreating the use cases or rebinding.
These are the observations from our tablet and scanner. The mapping detail belongs to this case; it is not a claim that the same implementation has been verified on every device.
The agent performed most of the mechanical work. It read the existing code, added diagnostics, collected device data, analyzed the logs, changed the implementation, repeated the checks, and updated the merge request.
My contribution included designing the experiment, physically rotating the tablet, evaluating the hypotheses, and deciding what would count as sufficient evidence.
At the critical moment, I did not know the right five lines of Kotlin. I could still recognize that “the value arrives later, so wait for it” was an incomplete explanation of the bug.
The useful intervention was to require the agent to show where the state diverged. That changed both the investigation and the fix.
For me, the reusable lesson is to make the verification criterion explicit: identify the states that should agree, capture their values during the failing behavior, and repeat the same experiment after the change. Here, that meant checking the display rotation, both use-case targets, and the image rotation together.
A coding agent can run much of that loop. Deciding what the experiment proves—and whether its evidence justifies accepting the fix—remains engineering work.
The full case study includes the debugging dialogue and a more detailed breakdown of how we divided the work.