MetaMask / MetaMask/metamask-mobile

Use native-stack for the heavy AppFlow/Main navigators instead of JS stack

Open
#31,327 1 comment 0 reactions 0 assignees View on GitHub
area-performance ta-triaged team-mobile-platform
Dominant language
TypeScript
Stars
3k
Forks
1.7k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

> **Performance audit finding** · Severity: **Medium** · Effort: Hard · Fix risk: Risky · Test safety net: Uncovered
> Owner: `@MetaMask/mobile-platform (suggested)`
> File: `app/components/Nav/App/App.tsx:3`

### What is this about?

The app's two largest navigators — the root `AppFlow` (`app/components/Nav/App/App.tsx`) and the post-login `MainNavigator` (`app/components/Nav/Main/MainNavigator.js`) — are built with `@react-navigation/stack` (the JavaScript-driven stack), not `@react-navigation/native-stack`. The native-stack package (already a dependency: `@react-navigation/native-stack: ^6.0.0`, see `package.json`) renders screens with the platform's native navigation primitives (`UINavigationController` / `Fragment`), running transitions and gesture handling off the JS thread, whereas the JS stack runs card transitions and the gesture/animation interpolators on the JS thread via Reanimated/Animated.

**Why it matters**

Every push/pop in these navigators (which carry ~70 and several dozen screens respectively) drives card-style transitions through JS-thread interpolators (`cardStyleInterpolator`), competing with controller state updates, list rendering, and websocket-driven re-renders that are common right after navigation. On lower-end Android devices this manifests as dropped frames during screen transitions. Native-stack offloads the transition/gesture work, freeing the JS thread during the most render-heavy moments.

### Scenario

N/A — see Technical Details.

### Design

N/A — internal performance change; no UI/design impact.

### Technical Details

**Evidence**

`app/components/Nav/App/App.tsx:3-6`:
```ts
import { createStackNavigator, type StackNavigationOptions } from '@react-navigation/stack';
// ...
const Stack = createStackNavigator();
```
`app/components/Nav/Main/MainNavigator.js:3` and `:161`:
```js
import { createStackNavigator } from '@react-navigation/stack';
// ...
const Stack = createStackNavigator();
```
Both navigators register a large number of `Stack.Screen`s and rely on JS `cardStyleInterpolator` transitions (e.g. `slideFromRightAnimation`, `fadeAnimation` in MainNavigator.js:171-208).

**Fix**

Migrate the heavy flows to `createNativeStackNavigator`. This is incremental: native-stack supports most screen options, and `presentation: 'modal'/'transparentModal'` map to native equivalents. Custom `cardStyleInterpolator` transitions must be replaced with native-stack `animation` options (`slide_from_right`, `fade`, etc.) or kept as JS stack only for the specific screens that genuinely need bespoke interpolators (e.g. the transparent account-selector overlay). Start with the leaf flows that have no custom interpolator, then evaluate the root.

### Threat Modeling Framework

N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.

### Acceptance Criteria

- 1. Build on a low/mid-tier Android device and record FPS (Perf Monitor / `react-native-performance`) during repeated push/pop on Settings and asset-detail screens before/after.
2. Confirm transition visuals and gesture-back still match design on both platforms.
3. Verify modal/transparent presentations (account selector, confirmation modals) still render correctly.

### References

- File: `app/components/Nav/App/App.tsx:3`
- Source: MetaMask Mobile performance audit — finding `nav-appflow-js-stack-vs-native-stack`
- Owner (CODEOWNERS / best-effort): @MetaMask/mobile-platform (suggested)
- Status: **UNVALIDATED**

Contributor guide

Open the contributing guide

Research direction

Start with app/components/Nav/App/App.tsx and app/components/Nav/Main/MainNavigator.js, then review the native-stack dependency in package.json and the existing transition options. Establish baseline and comparison builds on a low/mid-tier Android device using repeated Settings and asset-detail push/pop; done means matching transitions and back gestures on both platforms, with modal and transparent presentations still rendering correctly.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, react-native, typescript
Domain
frontend, mobile, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.