Summary
Opening Verify → Scan crashes every minified release build (2.0.x, any device). Debug builds aren't minified, so they never show it.
Fixes #878, fixes #970.
Root cause, from the retraced crash and the startup logcat of a release build:
QRCodeAnalyzer.<init> (VerificationSheet.kt)
BarcodeScanning.getClient() → NullPointerException: ML Kit was never initialized
because at startup:
MlKitInitProvider.onCreate
ComponentDiscovery: Could not instantiate CommonComponentRegistrar,
VisionCommonRegistrar, BarcodeRegistrar
NoSuchMethodException: <init> [] ← R8 full mode removed the no-arg constructorsThe fix keeps those constructors. It follows the same pattern as the existing Room rule:
# app/proguard-rules.pro
+-keepclassmembers class * implements com.google.firebase.components.ComponentRegistrar {
+ <init>();
+}The Scan tab also gets a fallback and feedback:
<ScanTabContent>
- <ScannerView onScan> # rejected codes did nothing
+ <ScannerView onScan = handleCode>
+ <TextButton "Paste verification URL"> → handleCode(clipboard text)
+ handleCode: success → toast "Verification requested"
+ failure → inline "Invalid or expired code, or this person isn't connected nearby"- Paste button: verifies a
bitchat://verifylink read with another QR app. This helps when the camera is unavailable or camera permission is denied. - Rejected camera scans: codes that are invalid, expired, or from a peer who isn't connected used to do nothing. They now show the same inline message. It's inline rather than a toast because Android 12+ cuts toasts to two lines.
- Strings: the button and success message reuse the existing, already-translated
verify_paste_labelandverify_scanned. The newverify_scan_failedis added to every locale, in English where that locale's verify strings are still in English.
Evidence
The tests below used a minified release build signed with the debug key, on an Android 16 emulator unless noted otherwise.
-
Before (
main):- Opening Scan closes the app.
- Startup logcat shows
Could not instantiate …Registrarthree times. - Removing
ImplementationMode.EMBEDDED, as #976 and #974 do, still crashes the same way.
After:
- No registrar warnings.
- The Scan tab shows the camera preview and nothing crashes.
- The same result on a physical Android 16 phone.
-
Camera path: a physical phone scanning an emulator's QR code shows the inline "not connected nearby" message. That's expected, because an emulator can't join a phone's mesh. It shows the camera and ML Kit decoding work end to end.
-
Successful verification: two emulators meshed over the emulator's virtual Bluetooth.
- Pasting emulator A's verify link on emulator B shows "Verification requested".
- B then lists A with the verified badge and the message "Verified ⟨A⟩".
-
Paste with an empty clipboard: shows the inline error and doesn't crash.
-
CI tasks:
testDebugUnitTestpasses (638 tests, 0 failures).lintDebugreports no new issues in the changed files.
Mesh Lab wasn't run: no transport, routing, protocol or crypto code changed.
Merge Danger
Door: two-way
Blast Radius: small
- The keep rule only matches classes implementing
ComponentRegistrar, which here are ML Kit's three registrars. - The UI change is limited to the Scan tab.
- Related PRs #976, #900 and #899 change the CameraX viewfinder setup but don't address this crash. For #976's change, I checked this on the release build.
🤖 Generated with Claude Code
Activity
4But I have already successfully tested it on Android 16
Review: looks good to merge ✅
I confirmed the root cause with an R8 build. I ran minifyReleaseWithR8 with main's proguard-rules.pro and then with this PR's rules.
- With main's rules, the mapping keeps
CommonComponentRegistrar,VisionCommonRegistrarandBarcodeRegistrarbut drops their no-arg<init>(). Thefirebase-componentsconsumer rule (-keep class * implements ComponentRegistrar) keeps the classes but not the constructors in R8 full mode. - With this PR's rules, all three keep
<init>().
I also checked every other reflection-loaded <meta-data> class in the merged release manifest: the CameraX config provider, WorkManager, the lifecycle, emoji and profile installers, and the okhttp initializer. They all keep their constructors under main's rules, so only these three registrars needed the new rule.
Nits (non-blocking):
- Paste with an empty clipboard, or scanning a QR code that isn't a bitchat code, shows the "invalid or expired" message. A separate message would be clearer.
BarcodeScanning.getClient()still runs unguarded insideremember {}. If ML Kit fails for some other reason, the screen still crashes instead of falling back to paste.- About 17 of the 33 locale files get the English text copied in (ar, bn, fa, fil, hi, id, ka, ko, mg, ne, nl, pa-rPK, sv, th, tr, ur, vi). At runtime this is the same as falling back to English, but it hides the string from MissingTranslation lint and translators. Prefer leaving those out. The new
values-eugets no entry. LocalClipboardManageris deprecated (warning only).
No CI has run on this PR yet; it needs workflow approval. It merges cleanly with main. It overlaps #984 in proguard-rules.pro (same keep rule), so only one of them should carry it.
Codex Review Summary
This comment shows the latest Codex review activity on this pull request.
5160efcℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.