I am building barcode scanning functionality into an app, and have followed this guide: https://learntodroid.com/how-to-create-a-qr-code-scanner-app-in-android/.
I have a main activity, which launches my QR scanning activity. When I detect a particular uri from the QR code (based on scheme, host and path), I want to end the QR scanning activity, return to my main activity and launch a dialog.
This is all working - the first time. However, if I relaunch the scanning activity, it no longer detects QR codes, and my onQRCodeFound never gets hit. (Killing and restarting the app resets it, I can scan 1 QR code successfully again, but then it stops detecting them if I reopen the QR activity). The image preview is shown, but the QR never gets recognised.
I do get W/System.err: com.google.zxing.NotFoundException
printed to the log repeatedly while the activity is open.
UPDATE On more investigation I found that on the 2nd (and subsequent times) I launch this activity the previewView has a width and height of 0 (the first time, it's correctly getting the size of 1080x2280). Setting a target resolution of 0x0 for the ImageAnalysis was the problem.
If I hard code the resolution, rather than using previewView.getWidth() and previewView.getHeight(), then it works fine every time.
If I change the call to bindCameraPreview(cameraProvider)
to previewView.post(()->bindCameraPreview(cameraProvider))
it works.
Not sure if this is the best/correct solution, but it's working for me. I still don't understand why my previewView has the correct dimensions the first time, and 0s the 2nd - if anyone knows, please enlighten me!
END UPDATE
Here is my ScanQRActivity:
public class ScanQRActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CAMERA = 0;
private PreviewView previewView;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private Button qrCodeFoundButton;
private String qrCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qr_scan);
previewView = findViewById(R.id.activity_main_previewView);
qrCodeFoundButton = findViewById(R.id.activity_main_qrCodeFoundButton);
qrCodeFoundButton.setVisibility(View.INVISIBLE);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
requestCamera();
}
private void requestCamera() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
startCamera();
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(ScanQRActivity.this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CAMERA) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCamera();
} else {
// Toast.makeText(this, "Camera Permission Denied", Toast.LENGTH_SHORT).show();
}
}
}
private void startCamera() {
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindCameraPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
Toast.makeText(this, "Error starting camera " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}, ContextCompat.getMainExecutor(this));
}
private void bindCameraPreview(@NonNull ProcessCameraProvider cameraProvider) {
previewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
Preview preview = new Preview.Builder()
.build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(previewView.getWidth(), previewView.getHeight() ))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new QRCodeImageAnalyzer(new QRCodeImageAnalyzer.QRCodeFoundListener() {
@Override
public void onQRCodeFound(String _qrCode) {
qrCode = _qrCode;
qrCodeFoundButton.setVisibility(View.VISIBLE);
Uri code = Uri.parse(qrCode);
if(code.getScheme().equals("myCustomScheme") && code.getHost().equals("app")){
String path = code.getPath();
String host = code.getHost() ;
String query = code.getQuery();
List<String> params = code.getQueryParameters("a");
if(path.equals("/voucher")){
Intent intent = new Intent();
intent.putExtra("type", "voucher");
intent.putExtra("voucherCode", code.getQueryParameter("c"));
intent.putExtra("timestamp", code.getQueryParameter("t"));
setResult(Activity.RESULT_OK, intent);
finish();
}
}
}
@Override
public void qrCodeNotFound() {
}
}));
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector,imageAnalysis, preview);
}
}
And my ActivityResultLauncher:
ActivityResultLauncher<Intent> startQRActivity = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
// Add same code that you want to add in onActivityResult method
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
String code = data.getStringExtra("voucherCode");
String time = data.getStringExtra("timestamp");
voucherDetailBusinessDialog dia = new voucherDetailBusinessDialog(getColor(R.color.appThemeColor), getColor(R.color.orange), code);
dia.show(getSupportFragmentManager(),null);
}
}
});