📈 - Add EAS Observe for startup performance metrics#636
Conversation
Install expo-observe and wire it into the root layout: wrap with ObserveRoot for Time to First Render, and call markInteractive() once the store and locale are ready for Time to Interactive. Also bump app version to 2.7.1. Claude-Session: https://claude.ai/code/session_01HGgWseHtmPxgmsJuhXyWB5
There was a problem hiding this comment.
Code Review
This pull request integrates expo-observe into the application to track when the app becomes interactive, wrapping the root layout with ObserveRoot and bumping the app version to 2.7.1. The feedback suggests using a useRef to guarantee that markInteractive() is called exactly once when the store and locale are ready, preventing potential duplicate signals.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const [localeReady, setLocaleReady] = useState(false) | ||
| const appState = useRef(AppState.currentState) | ||
| const router = useRouter() | ||
| const { markInteractive } = useObserve() |
There was a problem hiding this comment.
To prevent potential duplicate calls to markInteractive() (for instance, if the markInteractive function returned by useObserve() is not stable across renders, or if other dependencies change), it is safer to use a useRef to guarantee that the interactive signal is sent exactly once.
| const { markInteractive } = useObserve() | |
| const { markInteractive } = useObserve() | |
| const hasMarkedInteractive = useRef(false) |
| useEffect(() => { | ||
| // Signal EAS Observe that the app is interactive once the store + locale are | ||
| // ready — this is the point we stop returning null and render the real UI. | ||
| if (storeReady && localeReady) { | ||
| markInteractive() | ||
| } | ||
| }, [storeReady, localeReady, markInteractive]) |
There was a problem hiding this comment.
Use the hasMarkedInteractive ref to ensure markInteractive() is only invoked once when both the store and locale become ready.
| useEffect(() => { | |
| // Signal EAS Observe that the app is interactive once the store + locale are | |
| // ready — this is the point we stop returning null and render the real UI. | |
| if (storeReady && localeReady) { | |
| markInteractive() | |
| } | |
| }, [storeReady, localeReady, markInteractive]) | |
| useEffect(() => { | |
| // Signal EAS Observe that the app is interactive once the store + locale are | |
| // ready — this is the point we stop returning null and render the real UI. | |
| if (storeReady && localeReady && !hasMarkedInteractive.current) { | |
| hasMarkedInteractive.current = true | |
| markInteractive() | |
| } | |
| }, [storeReady, localeReady, markInteractive]) |
Summary
Adds EAS Observe to track app-startup performance (cold/warm launch, TTR, TTI) from production builds.
Changes
expo-observe@~56.0.21(SDK 56-compatible).ObserveRoot.wrap(...)(composed with the existingSentry.wrap) — measures Time to First Render automatically.markInteractive()via theuseObserve()hook oncestoreReady && localeReady— i.e. exactly when the app stops renderingnulland shows real UI — to record Time to Interactive.2.7.1.Notes
eas build, under the Observe tab in the EAS dashboard.markInteractive()is placed in the root ready-gate, covering normal launches. Deep-link launches into a modal/screen aren't separately tracked yet — the optional per-route Expo Router integration could be added later if we want per-screen TTI.https://claude.ai/code/session_01HGgWseHtmPxgmsJuhXyWB5