FormidableLabs / FormidableLabs/react-native-app-auth

Android: opt-in Partial Custom Tab (bottom-sheet) for the authorization flow

Open
#1,118 0 comments 0 reactions 0 assignees View on GitHub
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) |
| --- | --- |
| iOS ASWebAuthenticationSession bottom-sheet | 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) |
| --- |
| Android Partial Custom Tab bottom-sheet |

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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.