QR Code Scanning¶
This document explains how QR code scanning is implemented and exposed to the WebView layer. It covers:
- The bridge API used by JavaScript to start a scan
- How permissions are handled
- How the Huawei ScanKit activity is launched
- How the scan result is delivered back to JavaScript
The QR scanning feature is implemented in BaseWebViewActivity and exposed to the WebView via bridgeWebView.
Overview¶
The QR scanning flow is:
┌──────────┐
│ START │
└────┬─────┘
│
▼
┌─────────────────────────┐
│ Check Permissions │
│ - CAMERA │
│ - READ_MEDIA │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ Register Callback │
│ scanQrcodeResultCallback│
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ startQrCodeScan(999) │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ Open Camera Scanner │
│ │
│ ┌─────────────────┐ │
│ │ [QR Frame] │ │
│ │ │ │
│ │ Point camera │ │
│ │ at QR code │ │
│ └─────────────────┘ │
└────┬────────────────────┘
│
│ (User scans or cancels)
│
├────────────┬─────────────┐
▼ ▼ ▼
Success Cancel Error
│
▼
┌─────────────────────────┐
│ Decode QR Data │
│ Extract value │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ scanQrcodeResultCallback│
│ { │
│ value: "QR_CONTENT", │
│ requestCode: 999 │
│ } │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ Process QR Data │
│ in JavaScript │
│ - URL redirect │
│ - Parse JSON │
│ - etc. │
└─────────────────────────┘
│
▼
┌──────────┐
│ END │
└──────────┘
- JavaScript calls the native bridge method startQrCodeScan.
- Native code checks and requests the necessary permissions (camera, etc.).
- If permissions are granted, native configures Huawei ScanKit and launches the ScanKitActivity.
- The user scans a QR code (or cancels).
- BaseWebViewActivity.onActivityResult() receives the result, extracts the scanned value, and sends it back to JavaScript via scanQrcodeResultCallBack.
The flow is fully event-driven: the WebView triggers the scan and receives the result asynchronously through a callback.
Native–WebView Bridge API¶
startQrCodeScan (JS → Native)
Bridge handler name: startQrCodeScan
Defined in: registerMethod() inside BaseWebViewActivity
Signature on the native side:
bridgeWebView.registerHandler("startQrCodeScan", new BridgeHandler() {
@Override
public void handler(String requestCode, CallBackFunction function) {
// ...
}
});
Parameters¶
requestCode (String)
- Optional, sent from JavaScript.
- If empty, REQUEST_CODE_SCAN_ONE defaults to 0.
- If non-empty, it is parsed as an integer and stored in REQUEST_CODE_SCAN_ONE.
- This allows the JS side to distinguish between multiple scan requests if needed.
Example native logic:
REQUEST_CODE_SCAN_ONE = TextUtils.isEmpty(requestCode)
? 0
: Integer.valueOf(requestCode);
Immediate Native Behavior¶
Inside handler(...), native code:
- Requests runtime permissions using
XXPermissions(with a custom PermissionInterceptor). - If not all required permissions are granted, it immediately calls back to JS with a permission error:
function.onCallBack(gson.toJson(Result.fail(PERMISSION_ERROR, false))); return; - If permissions are granted, it builds a Huawei ScanKit configuration and launches the scan activity.
Permissions Handling¶
Permissions are requested via XXPermissions:
XXPermissions.with(BaseWebViewActivity.this)
// (Camera and any related permissions are configured here)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
if (!allGranted) {
function.onCallBack(gson.toJson(Result.fail(PERMISSION_ERROR, false)));
return;
}
// Permissions granted → proceed to launch scanner
// ...
}
});
Key points:¶
- Permissions are requested before any scan UI is launched.
- If the user denies any required permission, the WebView receives a response with
Result.fail(PERMISSION_ERROR, false)and no scan activity is started. - The actual permission list (camera, storage, etc.) is configured in the native code and can be adjusted as needed.
Launching the Scan UI (Huawei ScanKit)¶
Once permissions are granted, the code constructs an HmsScanAnalyzerOptions and starts the Huawei ScanKit activity:
HmsScanAnalyzerOptions.Creator creator = new HmsScanAnalyzerOptions.Creator();
// Accept all types of barcodes / QR codes
creator.setHmsScanTypes(HmsScan.ALL_SCAN_TYPE);
// Show the built-in scan guide overlay
creator.setShowGuide(true);
// Allow scanning from gallery (photo mode)
creator.setPhotoMode(true);
HmsScanAnalyzerOptions hmsScanAnalyzerOptions = creator.create();
// Launch ScanKit activity
Intent intent = new Intent(BaseWebViewActivity.this, ScanKitActivity.class);
if (intent != null) {
intent.putExtra("ScanFormatValue", hmsScanAnalyzerOptions.mode);
}
startActivityForResult(intent, REQUEST_CODE_SCAN_ONE);
Notes:
HmsScan.ALL_SCAN_TYPEenables recognition of multiple barcode formats, not just QR codes.setShowGuide(true)displays a guide on top of the camera preview to help user alignment.setPhotoMode(true)allows the user to select an existing image from the gallery and scan it.- The
REQUEST_CODE_SCAN_ONEis used later inonActivityResultto identify the result.
If any exception occurs while building options or starting the activity, it is caught and logged:
}
catch (Exception e) {
e.printStackTrace();
}
Receiving the Scan Result (Native)¶
The scan result is handled in onActivityResult:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SCAN_ONE && resultCode == RESULT_OK) {
HmsScan obj = data.getParcelableExtra(ScanUtil.RESULT);
Toaster.show(obj.originalValue);
if (obj != null) {
Map<String, Object> map = new HashMap<>();
map.put("value", obj.originalValue);
map.put("requestCode", REQUEST_CODE_SCAN_ONE);
bridgeWebView.callHandler("scanQrcodeResultCallBack",
gson.toJson(Result.ok(map)),
new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
} else {
bridgeWebView.callHandler("scanQrcodeResultCallBack",
gson.toJson(Result.fail()),
new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
}
}
// ... (other onActivityResult branches: picture choose, OCR, etc.)
}
Important details¶
- Only when
requestCode == REQUEST_CODE_SCAN_ONE andresultCode == RESULT_OKdo we treat it as a valid scan result. HmsScan obj = data.getParcelableExtra(ScanUtil.RESULT);pulls theHmsScanresult object from the intent.obj.originalValuecontains the string value encoded in the QR code / barcode.- A small toast (
Toaster.show(obj.originalValue);) is shown for debugging / quick feedback. -
If
objis non-null:- A
mapis created with:"value"→ the scanned string (obj.originalValue)"requestCode"→ the request code used (so JS can correlate the response)
- The map is wrapped with
Result.ok(map)and sent back to JS viascanQrcodeResultCallBack.
- A
-
If
objis null:- Native calls scanQrcodeResultCallBack with
Result.fail().
- Native calls scanQrcodeResultCallBack with
Cancellation handling:
- If the user cancels the ScanKit screen, `resultCode` will not be `RESULT_OK`, so the QR branch is skipped.
- In that case, no success payload is sent; the JS side should handle this by treating “no result received” as a canceled operation or by adding a separate mechanism if needed.
JavaScript Side Usage¶
1. Register the result callback¶
In your WebView JavaScript, you must register a handler to receive scan results
bridgeWebView.registerHandler("scanQrcodeResultCallBack", function (data) {
const res = JSON.parse(data);
// Depending on your Result implementation, this may look like:
// { success: true/false, code: ..., data: { value, requestCode } }
if (res.success) {
const value = res.data.value;
const reqCode = res.data.requestCode;
console.log("QR scan success:", value, "(req:", reqCode, ")");
// TODO: handle the scanned value (navigate, fill form, etc.)
} else {
console.warn("QR scan failed", res);
// TODO: show error message or retry option
}
});
2. Start a QR scan from JS¶
When you want to start scanning:
function startQrScan() {
const REQUEST_CODE_QR = 1001; // any integer you want to track this call
bridgeWebView.callHandler(
"startQrCodeScan",
String(REQUEST_CODE_QR), // this becomes requestCode in native
function (response) {
// Initial response from native (permission / immediate errors)
const res = JSON.parse(response || "{}");
if (res.success === false) {
console.error("Failed to start QR scan:", res);
// Probably permission denied or some immediate error.
} else {
// In most cases, you simply wait for scanQrcodeResultCallBack.
console.log("QR scan started; waiting for result...");
}
}
);
}
Flow from JS perspective:
- Call
startQrCodeScan. - Optionally inspect the immediate response (for permission errors).
- Wait for
scanQrcodeResultCallBackwith the final result.
Result Payload Structure¶
All responses are wrapped in the internal Result class before being sent to JS, for example:
-
Success (conceptual shape):
{ "success": true, "code": 0, "data": { "value": "scanned-qr-content", "requestCode": 1001 } } -
On Faliure
{ "success": false, "code": "<error-code>", "message": "<human-readable message>" }
The exact keys (code, message, etc.) depend on your Result implementation, but the important part is that data.value holds the scanned string and data.requestCode lets you map a result to a specific scan request.
Error Handling & Edge Cases¶
-
Permissions denied
startQrCodeScanreturns immediately withResult.fail(PERMISSION_ERROR, false).- The scan UI is never opened.
- JS should show a message asking the user to grant camera permission.
-
Scan returns null result (
obj == null)scanQrcodeResultCallBackis called withResult.fail().- JS should treat this as “scan failed” and allow retry.
-
User cancels ScanKit
resultCodeis notRESULT_OK.- QR branch is skipped in
onActivityResult. - No success payload is sent; JS should handle this as “no result” (e.g. by timeout, or by user flow).
-
Unexpected exceptions when launching ScanKit
- Currently only logged via
e.printStackTrace(). - You may want to extend this to send a failure
Resultback to JS for better UX.
- Currently only logged via
Summary¶
-
The QR scanning implementation provides a clean, high-level API to the WebView:
- JS entry point:
startQrCodeScan - Native scanner: Huawei ScanKit via
ScanKitActivity - Result callback to JS:
scanQrcodeResultCallBackwith a Result-wrapped payload containing the scanned value and requestCode.
- JS entry point:
This keeps all camera and ScanKit details in the native layer, while keeping the WebView integration simple and predictable.