FormidableLabs / FormidableLabs/react-native-app-auth
Android: opt-in Partial Custom Tab (bottom-sheet) for the authorization flow
- Dominant language
- Java
- Stars
- 2.3k
- Forks
- 473
- PR merge metrics
- No merged PRs in 30d
Description
## Problem
On iOS, the authorization flow opens as a modal sheet over the host app via
`ASWebAuthenticationSession`. The user never feels like they've left the app:
the host's wallpaper is visible behind the sheet, animations are continuous,
and dismissal returns control to the same screen.
On Android, the equivalent flow opens a Chrome Custom Tab in **full-screen
mode**. The host activity is fully obscured. To users this reads as "the app
opened a browser" rather than "the app is asking me to sign in," which is
both a UX downgrade vs. iOS and a real cognitive interruption — especially
on the first sign-in / connect-account flow.
| iOS (today, no library change) | Android (today, no library change) |
| --- | --- |
|
| Full-screen Chrome Custom Tab — fully obscures the host app. |
Chrome 107 (Oct 2022) shipped the [Partial Custom Tabs API][partial-cct]
that closes this gap: it renders the Custom Tab as a user-resizable
bottom sheet at a caller-specified initial height. With it, the Android
side can match iOS's modal feel:
| Android with Partial Custom Tab (this proposal) |
| --- |
|
|
The feature is opt-in (default behavior unchanged) and gracefully degrades:
older Chrome silently ignores the extra and falls back to the full-screen
Custom Tab — i.e. zero regression risk for users on Chrome < 107.
[partial-cct]: https://developer.chrome.com/docs/android/custom-tabs/guide-partial-custom-tabs
## Proposal
Add one optional `AuthConfiguration` field, Android-only:
```ts
androidCustomTabPartialHeightFraction?: number; // (0, 1]
```
Semantics:
- **Unset** (default) — no change. `CustomTabsIntent.Builder` is built as
today; Chrome renders the full-screen Custom Tab.
- **Set to a fraction in `(0, 1]`** (e.g. `0.85`) — between
`authService.createCustomTabsIntentBuilder()` and `.build()`, the library
calls `setInitialActivityHeightPx(displayHeight * fraction,
CustomTabsIntent.ACTIVITY_HEIGHT_ADJUSTABLE)`. Chrome 107+ renders the
bottom sheet; older Chrome ignores the extra and falls back to
full-screen. The activity stays user-resizable to full screen via the
drag handle (`ACTIVITY_HEIGHT_ADJUSTABLE`).
- Skipped when `androidTrustedWebActivity` is true (TWAs intentionally
render full-screen).
A fraction is preferred over raw pixels because it's display-agnostic — the
caller doesn't have to reach for `Dimensions.get('window').height` or worry
about `dp` vs. `px`. Chrome enforces a 50% floor internally, so values
below that are auto-clamped by Chrome (this is documented behavior, not a
library concern).
## Implementation notes
- **androidx.browser** bumps `1.4.0 → 1.5.0` for the
`setInitialActivityHeightPx` API. The bump is API-compatible and 1.5.0
has been stable since late 2022.
- **First-launch warmup caveat (worth documenting next to the new option):**
Chrome's Partial CCT requires the `CustomTabsIntent.Builder` to be
constructed with an established `CustomTabsSession` to render as a
bottom sheet on the *first* invocation. Otherwise Chrome's first launch
falls back to full-screen and only subsequent launches get the sheet.
The library already exposes
[`prefetchConfiguration`](https://commerce.nearform.com/open-source/react-native-app-auth/docs/usage/prefetch) — calling
it on the screen that will later invoke `authorize()` is sufficient
(verified on a real Pixel emulator running Chrome 147). The PR's docs
update mentions this explicitly.
- No iOS counterpart needed — `ASWebAuthenticationSession` already
presents as a modal sheet by design. The new option is silently ignored
on iOS (guarded by `Platform.OS === 'android'`).
- No tests need mocking the native side — the JS layer's
responsibility is validation + correct positional argument to the
native bridge, both covered by spec additions.
## Backward compatibility
- Field is optional with default `null` (no behavior change when unset).
- Existing API surface untouched: no field renamed, removed, or whose
meaning shifted.
- Existing tests pass after pinning the new positional arg.
- Older Chrome / older Android versions: extra is ignored by Chrome, no
crash, falls back to current full-screen Custom Tab behavior.
## PR
A PR with the full implementation (TS types, JS validation, Java wiring,
README/docs, 5 new spec tests covering validation + bridge passthrough,
plus a changeset for the minor bump) is ready and will be linked below.
Happy to iterate on the API shape (e.g. accept an object form, expose
`resizeBehavior`, etc.) if maintainers prefer a different surface.
Contributor guide
Assessment
This issue has not been assessed yet.