Refactor Onfido native loader to ActivityIndicator - #96819
Conversation
Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
The only failing check is PR Author Checklist / checklist, and it's not a code failure — nothing to push a fix for. It's failing because it's a human-only checklist: the log shows Every actual CI check is green — ( |
|
@MelvinBot add Tests/QA steps |
|
Here are suggested Tests and QA Steps for this change. Since the edit is in Tests
QA StepsSame as Tests (native only). Why native-onlyThe only changed file is
|
|
@MelvinBot please update QA steps and check all items on the author checklist (Do NOT leave any item unchecked). I also verified this across all platforms. |
|
🤖 Done — updated the PR body:
Please add the screenshots/videos to the Screenshots/Videos section before marking it ready for review. |
|
@MelvinBot mark as ready for review |
|
🤖 Done — marked this PR as ready for review. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59547174e0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| <ActivityIndicator | ||
| size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE} | ||
| style={styles.flex1} | ||
| reasonAttributes={{context: 'Onfido'}} | ||
| /> |
There was a problem hiding this comment.
Guard pending Onfido start after Back is exposed
Now that this inline ActivityIndicator no longer blocks the header, users can tap Back while OnfidoSDK.start() is still pending, but the effect above has no cleanup/ignore-after-unmount guard. In the native callers I checked, those late callbacks still navigate or mutate wallet/bank-account state (OnfidoStep passes goBack/verifyIdentity, and the verify-identity pages update drafts or call onBackButtonPress), so backing out during initialization can still let a later SDK resolve/reject open the flow, navigate a second time, or submit/show an error after the user has left. Please cancel or ignore the pending promise after unmount before exposing Back during loading.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
There was a problem hiding this comment.
🤖 Addressed in 1238fa0.
Good catch — with Back now tappable during loading, a late OnfidoSDK.start() resolve/reject could navigate or mutate wallet/bank-account state after the user has already left. I added an isActive guard that the effect's cleanup flips to false on unmount, and routed every callback (onSuccess / onUserExit / onError, including the Android relaunch path and the iOS permission Alert handlers) through guarded wrappers that no-op after unmount. See src/components/Onfido/index.native.tsx:26-47 and the cleanup at src/components/Onfido/index.native.tsx:133-135.
ActivityIndicator cleanly unmounts and stops rendering; the SDK promise chain itself can't be cancelled, but its callbacks are now ignored once the component is gone, so backing out during initialization no longer double-navigates or submits/errors after exit.
|
The only failing check on this PR is PR Reviewer Checklist / checklist (the All actual build/test checks are green (typecheck, ESLint, Bun/Jest tests, Storybook, perf, etc.). To clear it: the assigned reviewer needs to complete the PR Reviewer Checklist. Once that's checked off, this check will pass. |
|
Nothing has changed since my last comment: the only red check is PR Reviewer Checklist / checklist, which fails until a reviewer completes the reviewer checklist on this PR. It's not a code failure and can't be cleared by a push — all build/test checks are green. To clear it: the assigned reviewer needs to complete the PR Reviewer Checklist. |
Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com>
|
@Julesssss Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridApp96819.moviOS: mWeb SafariMacOS: Chrome / Safari |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1238fa0cfb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| { | ||
| text: translate('common.cancel'), | ||
| onPress: () => onUserExit(true), | ||
| onPress: () => handleUserExit(true), |
There was a problem hiding this comment.
Skip stale permission alerts after Back
On iOS with camera or microphone permission denied, backing out while OnfidoSDK.start() is pending can still let the async permission-check branch show Alert.alert after this component has unmounted. The fresh evidence after the follow-up fix is that isActive is only checked inside handleUserExit/handleError (including this alert button callback), so the global alert itself is still created on whatever screen the user returned to; skip the permission-alert branch when !isActive before calling Alert.alert.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Yes, this is valid. The isActive guard added in the follow-up covers handleSuccess/handleUserExit/handleError, but the iOS permission branch calls Alert.alert(...) with no isActive check in front of it.
Since Back is now exposed while OnfidoSDK.start() is pending, this window is reachable: user denies camera/mic → backs out (component unmounts, isActive = false) → start() rejects → the .catch enters the iOS branch → checkMultiple(...).then(...) resolves and fires Alert.alert. Alert.alert is a global native prompt, not tied to this component, so the stale permission alert pops up on whatever screen the user returned to. Guarding only the button onPress callbacks doesn't stop the alert from appearing — it just no-ops the exit action if a button is tapped.
Fix as suggested: short-circuit when unmounted before presenting the alert, e.g. add if (!isActive) { return; } at the top of the checkMultiple(...).then((statuses) => { ... }) callback (line 88), which also keeps the handleError fallback in that same callback from running after unmount.
There was a problem hiding this comment.
🤖 Addressed in 45f9f21.
Good catch — the isActive guard was only inside handleUserExit/handleError, so the iOS permission branch still built and presented Alert.alert(...) even after unmount. Added an early if (!isActive) return; at the top of the checkMultiple(...).then(...) callback so the stale permission prompt is skipped once the user has backed out during a pending OnfidoSDK.start():
checkMultiple([PERMISSIONS.IOS.MICROPHONE, PERMISSIONS.IOS.CAMERA])
.then((statuses) => {
// The permission check resolves asynchronously, so the user may have already backed out (unmounting this
// component) by the time it settles. Skip the alert to avoid showing a stale permission prompt on the screen they returned to.
if (!isActive) {
return;
}
...npm run fmt and lint-changed are clean. The typecheck failures in this environment are pre-existing ResultMetadata generic errors in unrelated files (card/sidebar hooks) — none in Onfido/index.native.tsx. This is native-only and can't be exercised on the web build.
|
🤖 Code review — LGTM. The loader swap and the unmount guard both look correct. 1. Loader refactor (
2. Unmount guard ( Minor, non-blocking
Testing note: this diff only affects the native code path, so the changed lines can't be exercised on the web build — it needs an iOS/Android native build to verify visually. I don't approve/merge PRs — leaving the formal approval to a human reviewer. |
… pending start
Co-authored-by: {"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"} <{"message":"Not Found","documentation_url":"https://docs.github.com/rest/issues/comments#get-an-issue-comment","status":"404"}@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45f9f21e48
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return () => { | ||
| isActive = false; | ||
| }; |
There was a problem hiding this comment.
Cancel native Onfido launch after Back exits loading
Fresh evidence after the follow-up fix is that unmount cleanup now only flips isActive; it never cancels the already-issued OnfidoSDK.start(...) call, whose API launches the native flow and only settles the promise when the SDK session succeeds or exits. When a user taps the now-exposed header Back during the initialization spinner, this component unmounts but the native SDK can still present the capture flow over the previous screen; the new guards only suppress later JS callbacks. Please keep Back unavailable until the native flow has presented, or add a real cancellation/launch guard before relying on this cleanup.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
@MelvinBot is this valid case?
I think this loading page is hidden by onfido step pages so unlikely to see back button during onfido flow.
There was a problem hiding this comment.
Partly valid — the underlying race is real, but the mental model in your rebuttal has one gap worth calling out.
Why it's a real race: I checked the SDK surface — @onfido/react-native-sdk (v15.1.0) exposes no stop()/cancel()/dismiss() method. Once OnfidoSDK.start() fires, the only things that settle it are the native flow succeeding or the user exiting it inside the native UI. Our cleanup only flips isActive, which suppresses the JS callbacks — it can't cancel an in-flight native launch:
return () => {
isActive = false;
};So the bot is technically correct that a start() already issued isn't cancelled by unmounting.
The gap in "hidden by onfido step pages": the spinner + Back button are shown before the native Onfido UI presents (during start() init), not during/after it. In that pre-presentation window the native flow isn't up yet, so Back is reachable. Once the native flow presents it covers everything — but the race is precisely about tapping Back before that. So "you won't see Back during the onfido flow" is true; "you can't tap Back before it presents" is not.
But it's low severity, and I'd lean toward deferring rather than blocking:
- The
isActiveguards you already added neutralize the dangerous outcomes (no stale navigation or wallet/bank-account mutation from late callbacks). - The only residual symptom is cosmetic: on a slow enough init the native capture UI could present over the previous screen after the user left — and they can just exit it, with
onUserExitsuppressed so no state changes. - Whether that window is even reachable depends entirely on the native init duration (token validation + config fetch + camera setup), which I can't measure from code — near-instant on a fast connection, tappable on a slow one.
Since there's no SDK cancel to call, the only way to fully close it is to keep Back unavailable until the native flow presents — which partly works against this PR's goal of exposing Back. Given the guards already prevent the harmful effects, treating this as a follow-up/won't-fix seems reasonable rather than a blocker.
|
🤖 Regression check: no functional regressions found. The The most likely concern — the spinner rendering top-left instead of centered — does not apply: RN's Investigation detailsChange is native-only ( Verified safe:
Two low-severity differences (not regressions):
|
|
Both current failures are pre-existing on
Suggested next step: merge the latest |
|
Both current failures are unrelated to this PR — which only changes 1. 2. The All build/lint/other test checks are green. |
|
@MelvinBot pull main |
…/App into claude-onfidoActivityIndicator Co-authored-by: Situ Chandra Shil <situchan@users.noreply.github.com>
|
🤖 Merged |
Explanation of Change
Refactors the
Onfidonative component's loader fromFullscreenLoadingIndicatortoActivityIndicator, in preparation for making the error/loading page recoverable (enabling the Go Back button by default).Per UI-1,
FullscreenLoadingIndicatorrenders as anabsoluteFilloverlay that covers the visible header/back button. All three consumers of this shared component render a header behind the loader:pages/ReimbursementAccount/USD/Requestor/VerifyIdentity/VerifyIdentity.tsx(InteractiveStepWrapper)pages/EnablePayments/Wallet/VerifyIdentity/VerifyIdentity.tsx(HeaderWithBackButton)pages/EnablePayments/Pay/Onfido/OnfidoStep.tsx(HeaderWithBackButton)So the correct loader here is
ActivityIndicator, which fills the space below the header (style={styles.flex1}) and keeps navigation accessible while loading.This is one of two separate PRs for the linked issue (one per occurrence); the other refactors
pages/settings/Wallet/PersonalCards/steps/BankConnection/index.native.tsx.Fixed Issues
$ #96095
PROPOSAL:
Tests
Offline tests
Onfido identity verification requires a network connection, so there is no offline-specific behavior for this loader change. With no network, the Onfido SDK cannot initialize; the change only affects how the loading spinner is laid out (below the header, with the back button visible/tappable) while it initializes.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari