From 4e0a7cca51c5f870a2057e2b4abc558fe39a14b8 Mon Sep 17 00:00:00 2001 From: Krikorios Date: Mon, 24 Aug 2026 00:32:34 +0300 Subject: [PATCH] Fix map failing to render, dedupe expo-crypto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The home screen passed its FlatList header as `ListHeaderComponent={() => (...)}`. VirtualizedList renders a function-valued header prop as ``, so a fresh arrow function on each render is a fresh element type: React unmounted the entire header subtree — MapView included — and mounted a new one. Home re-renders several times on mount (useFetch loading->data, useUserLocation pending->granted, session resolve), and recreating the Android GoogleMap surface each time left it grey with the Google logo and tiles that never finished loading. Pass the header as an element instead so the type stays stable and MapView mounts once. The same `() => (...)` pattern in ListEmptyComponent here, and in rides.tsx and confirm-ride.tsx, cost needless remounts of list chrome and DriverCards; fixed alongside. Also pin expo-crypto to ~13.0.2. It was ^57.0.1 — a wrong-major native module for SDK 51 that nothing in the app imports. npm hoisted it to the top level and gave expo-auth-session a nested 13.0.2 to satisfy its ~13.0.0 constraint, leaving two copies of one native module in the tree for autolinking to choose between. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 10 +++++++++- app.config.js | 19 ++++++++++++++++--- app/(root)/(tabs)/home.tsx | 18 +++++++++++------- app/(root)/(tabs)/rides.tsx | 12 +++++------- app/(root)/confirm-ride.tsx | 8 ++++---- package-lock.json | 22 +++++++--------------- package.json | 2 +- 7 files changed, 53 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 908d02c..e8ca9e3 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,15 @@ EXPO_PUBLIC_GEOAPIFY_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXX - Go to the [Google Cloud Console](https://console.cloud.google.com/). - Create a new project (or use an existing one). -- Navigate to the "APIs & Services" section and enable the required APIs (Places API, Directions API). +- Navigate to the "APIs & Services" section and enable **all** of the required APIs: + - **Maps SDK for Android** — draws the map itself. Without it Android renders an + empty grey tile area and logcat shows an authorization failure; the app looks + like the map "didn't load." + - **Maps SDK for iOS** — only needed if `components/map.tsx` is switched from + `PROVIDER_DEFAULT` (Apple Maps) to `PROVIDER_GOOGLE`. + - **Places API (New)** — destination search in `components/google-text-input.tsx`. + The legacy Places web service is unavailable to newer Google Cloud projects. + - **Directions API** — route polyline and driver ETAs. - Go to the "Credentials" tab and click on "Create Credentials." - Select "API Key." - Copy the generated **API Key** and add it to your `.env` file: diff --git a/app.config.js b/app.config.js index fbfa41d..f8b82e3 100644 --- a/app.config.js +++ b/app.config.js @@ -12,9 +12,22 @@ const LOCATION_PERMISSION = module.exports = ({ config }) => { if (!googleMapsApiKey) { - console.warn( - "[app.config] EXPO_PUBLIC_GOOGLE_API_KEY is not set — maps will render blank on Android.", - ); + const message = + "EXPO_PUBLIC_GOOGLE_API_KEY is not set — the Android manifest will have no " + + "Maps key and the map will render blank."; + + // `.env` is gitignored, so it is never uploaded to EAS. Unless the key is + // also registered as an EAS environment variable the build silently + // produces a keyless APK, and the blank map only shows up on the device. + // Fail the build here rather than shipping that. + if (process.env.EAS_BUILD) { + throw new Error( + `[app.config] ${message} Register it with \`eas env:create\` (or in the ` + + "EAS dashboard) for this build profile.", + ); + } + + console.warn(`[app.config] ${message}`); } return { diff --git a/app/(root)/(tabs)/home.tsx b/app/(root)/(tabs)/home.tsx index 3f066e9..60142ff 100644 --- a/app/(root)/(tabs)/home.tsx +++ b/app/(root)/(tabs)/home.tsx @@ -47,7 +47,6 @@ const Home = () => { router.push("/(root)/find-ride"); }; - return ( { contentContainerStyle={{ paddingBottom: 100, }} - ListEmptyComponent={() => ( + ListEmptyComponent={ {!loading ? ( <> @@ -74,16 +73,21 @@ const Home = () => { )} - )} - ListHeaderComponent={() => ( + } + // Passed as an *element*, not as `() => (...)`. VirtualizedList + // renders a function prop as ``, so a fresh arrow + // function each render is a fresh element type: React unmounts the + // whole header — MapView included — and mounts a new one. Recreating + // the Android map surface on every re-render leaves it grey with the + // Google logo and tiles that never finish loading. + ListHeaderComponent={ <> - Welcome{" "} - {user?.name || user?.email} 👋 + Welcome {user?.name || user?.email} 👋 @@ -140,7 +144,7 @@ const Home = () => { Recent Rides - )} + } /> ); diff --git a/app/(root)/(tabs)/rides.tsx b/app/(root)/(tabs)/rides.tsx index a0adbb3..8275366 100644 --- a/app/(root)/(tabs)/rides.tsx +++ b/app/(root)/(tabs)/rides.tsx @@ -23,7 +23,7 @@ const Rides = () => { contentContainerStyle={{ paddingBottom: 100, }} - ListEmptyComponent={() => ( + ListEmptyComponent={ {!loading ? ( <> @@ -39,12 +39,10 @@ const Rides = () => { )} - )} - ListHeaderComponent={() => ( - <> - All rides - - )} + } + ListHeaderComponent={ + All rides + } /> ); diff --git a/app/(root)/confirm-ride.tsx b/app/(root)/confirm-ride.tsx index ebaefd0..b8c7793 100644 --- a/app/(root)/confirm-ride.tsx +++ b/app/(root)/confirm-ride.tsx @@ -20,13 +20,13 @@ const ConfirmRide = () => { setSelected={() => setSelectedDriver(item.id)} /> )} - ListEmptyComponent={() => ( + ListEmptyComponent={ No drivers available on this route right now.{"\n"}Please try another destination. - )} - ListFooterComponent={() => ( + } + ListFooterComponent={ { className={selectedDriver === null ? "opacity-50" : ""} /> - )} + } /> ); diff --git a/package-lock.json b/package-lock.json index 28f0dd3..d1069dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "expo-auth-session": "~5.5.2", "expo-clipboard": "~6.0.3", "expo-constants": "~16.0.2", - "expo-crypto": "^57.0.1", + "expo-crypto": "~13.0.2", "expo-font": "~12.0.9", "expo-linking": "^6.3.1", "expo-location": "^17.0.1", @@ -9337,17 +9337,6 @@ "invariant": "^2.2.4" } }, - "node_modules/expo-auth-session/node_modules/expo-crypto": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-13.0.2.tgz", - "integrity": "sha512-7f/IMPYJZkBM21LNEMXGrNo/0uXSVfZTwufUdpNKedJR0fm5fH4DCSN79ZddlV26nF90PuXjK2inIbI6lb0qRA==", - "dependencies": { - "base64-js": "^1.3.0" - }, - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-clipboard": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-6.0.3.tgz", @@ -9368,9 +9357,12 @@ } }, "node_modules/expo-crypto": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-57.0.1.tgz", - "integrity": "sha512-xwegXQw3ATgeL1ZuqbSNrGzOeG+zNeh6Z6DSJk825Qpa3TEQQ1kG3ioE1p3g/SNF373BAVz2iBKUTSytlIbBRA==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-13.0.2.tgz", + "integrity": "sha512-7f/IMPYJZkBM21LNEMXGrNo/0uXSVfZTwufUdpNKedJR0fm5fH4DCSN79ZddlV26nF90PuXjK2inIbI6lb0qRA==", + "dependencies": { + "base64-js": "^1.3.0" + }, "peerDependencies": { "expo": "*" } diff --git a/package.json b/package.json index fc040cc..9d4b88b 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "expo-auth-session": "~5.5.2", "expo-clipboard": "~6.0.3", "expo-constants": "~16.0.2", - "expo-crypto": "^57.0.1", + "expo-crypto": "~13.0.2", "expo-font": "~12.0.9", "expo-linking": "^6.3.1", "expo-location": "^17.0.1",