Skip to content

Commit 398c93a

Browse files
authored
feat(expo-example): migrate to expo-router (#131)
1 parent 09425e7 commit 398c93a

30 files changed

Lines changed: 762 additions & 668 deletions

apps/expo-example/App.tsx

Lines changed: 0 additions & 56 deletions
This file was deleted.

apps/expo-example/app.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"backgroundColor": "#ffffff"
1313
},
1414
"scheme": "zerodev-example",
15+
"experiments": {
16+
"typedRoutes": true
17+
},
1518
"ios": {
1619
"supportsTablet": true,
1720
"bundleIdentifier": "com.zerodev.expoexample"
@@ -41,8 +44,9 @@
4144
]
4245
},
4346
"web": {
44-
"favicon": "./assets/favicon.png"
47+
"favicon": "./assets/favicon.png",
48+
"bundler": "metro"
4549
},
46-
"plugins": ["./plugins/withDebugKeystore"]
50+
"plugins": ["./plugins/withDebugKeystore", "expo-router"]
4751
}
4852
}

apps/expo-example/docs/oauth-native.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Either is fine. This repo supports both: `EXPO_PUBLIC_REDIRECT_URI`,
1414
when set, opts into the App Link path; when unset,
1515
`Linking.createURL('oauth-callback')` builds the custom-scheme fallback.
1616
Both branches land in the same `Linking.addEventListener('url', ...)`
17-
handler inside [`oauth/createNativeOAuthGetSessionId.ts`](../oauth/createNativeOAuthGetSessionId.ts).
17+
handler inside [`src/oauth/createNativeOAuthGetSessionId.ts`](../src/oauth/createNativeOAuthGetSessionId.ts).
1818

1919
This doc covers the App Link wiring. If you just want a custom scheme,
2020
leave `EXPO_PUBLIC_REDIRECT_URI` unset — no further setup needed.

apps/expo-example/docs/passkeys.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Much of the mechanics here follow Turnkey's
2323
| `android.package` | `app.json` | `com.zerodev.expoexample` |
2424
| Committed signing cert | `credentials/debug.keystore` | SHA-256 `11:F4:8E:00:20:B4:FB:77:BF:6C:86:A2:62:8F:F5:B4:69:6F:4C:68:ED:C0:90:79:56:93:6C:8E:50:4A:6E:B0` |
2525
| Gradle signing wiring | `plugins/withDebugKeystore.js` | Expo config plugin |
26-
| `rpId` | `wagmi.config.native.ts` | `zerodev-expo-example.vercel.app` |
26+
| `rpId` | `src/wagmi/config.native.ts` | `zerodev-expo-example.vercel.app` |
2727
| Assetlinks source | `assetlinks/public/.well-known/assetlinks.json` | Declares package + SHA-256 |
2828
| Live assetlinks | `https://zerodev-expo-example.vercel.app/.well-known/assetlinks.json` | Served by Vercel |
2929

@@ -90,7 +90,7 @@ Credential Manager will too.
9090

9191
The only hard requirements are HTTPS and that the file be served at
9292
the **root** `/.well-known/assetlinks.json`. If you prefer a different
93-
host, swap it in here and update `RP_ID` in `wagmi.config.native.ts`
93+
host, swap it in here and update `RP_ID` in `src/wagmi/config.native.ts`
9494
to match.
9595

9696
## Building a standalone APK (EAS local)

apps/expo-example/index.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

apps/expo-example/package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@zerodev/expo-example",
33
"version": "1.0.0",
4-
"main": "index.ts",
4+
"main": "expo-router/entry",
55
"scripts": {
66
"start": "expo start",
77
"android": "expo run:android",
@@ -20,16 +20,21 @@
2020
"babel-preset-expo": "^55.0.13",
2121
"expo": "~55.0.9",
2222
"expo-clipboard": "^55.0.9",
23+
"expo-constants": "~55.0.15",
2324
"expo-dev-client": "~55.0.28",
24-
"expo-linking": "^55.0.8",
25+
"expo-linking": "^55.0.14",
26+
"expo-router": "~55.0.13",
2527
"expo-secure-store": "^55.0.9",
26-
"expo-status-bar": "~55.0.4",
28+
"expo-splash-screen": "~55.0.19",
29+
"expo-status-bar": "~55.0.5",
2730
"expo-web-browser": "^55.0.10",
2831
"react": "19.2.0",
2932
"react-dom": "19.2.0",
3033
"react-native": "0.83.4",
3134
"react-native-get-random-values": "^2.0.0",
3235
"react-native-passkey": "^3.3.2",
36+
"react-native-safe-area-context": "~5.6.2",
37+
"react-native-screens": "~4.23.0",
3338
"react-native-web": "^0.21.2",
3439
"tslib": "^2.8.1",
3540
"uuid": "^13.0.0",
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'react-native-get-random-values'
2+
3+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4+
import { SplashScreen, Stack } from 'expo-router'
5+
import { StatusBar } from 'expo-status-bar'
6+
import { useEffect } from 'react'
7+
import { ActivityIndicator, View } from 'react-native'
8+
import { useAccount, useReconnect, WagmiProvider } from 'wagmi'
9+
import { wagmiConfig } from '@/wagmi/config'
10+
11+
SplashScreen.preventAutoHideAsync()
12+
13+
const queryClient = new QueryClient()
14+
15+
export default function RootLayout() {
16+
return (
17+
// `reconnectOnMount={false}` because wagmi's built-in reconnect runs
18+
// synchronously inside `<Hydrate>`'s render and mutates the store,
19+
// which surfaces as "Cannot update a component while rendering a
20+
// different component" warnings whenever this provider re-renders
21+
// (e.g. on expo-router navigation transitions). We trigger reconnect
22+
// ourselves from a mount effect below, after commit.
23+
<WagmiProvider config={wagmiConfig} reconnectOnMount={false}>
24+
<QueryClientProvider client={queryClient}>
25+
<App />
26+
<StatusBar style="auto" />
27+
</QueryClientProvider>
28+
</WagmiProvider>
29+
)
30+
}
31+
32+
function App() {
33+
const { address } = useAccount()
34+
const { reconnect, isSuccess, isError } = useReconnect()
35+
36+
// Manual replacement for `reconnectOnMount` (see WagmiProvider above).
37+
useEffect(() => {
38+
reconnect()
39+
}, [reconnect])
40+
41+
// Gate the UI on the reconnect mutation settling rather than on
42+
// `useConnection().status`. With `reconnectOnMount={false}` the store
43+
// initially reports `disconnected`, which would otherwise flash the
44+
// login screen for a moment before the saved session is restored.
45+
const isReady = isSuccess || isError
46+
47+
useEffect(() => {
48+
if (isReady) SplashScreen.hideAsync()
49+
}, [isReady])
50+
51+
if (!isReady) {
52+
return (
53+
<View
54+
style={{
55+
flex: 1,
56+
backgroundColor: '#E6F4FE',
57+
justifyContent: 'center',
58+
alignItems: 'center',
59+
}}
60+
>
61+
<ActivityIndicator size="large" color="#4285F4" />
62+
</View>
63+
)
64+
}
65+
66+
return (
67+
<Stack screenOptions={{ headerShown: false }}>
68+
<Stack.Protected guard={address !== undefined}>
69+
<Stack.Screen name="index" />
70+
</Stack.Protected>
71+
<Stack.Protected guard={address === undefined}>
72+
<Stack.Screen name="login" />
73+
<Stack.Screen name="oauth-callback" options={{ animation: 'none' }} />
74+
</Stack.Protected>
75+
</Stack>
76+
)
77+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ScrollView, StyleSheet, View } from 'react-native'
2+
import { ChainSwitcher } from '@/components/ChainSwitcher'
3+
import { ConnectionStatusBar } from '@/components/ConnectionStatusBar'
4+
import { SendTransaction } from '@/components/SendTransaction'
5+
6+
export default function HomeScreen() {
7+
return (
8+
<View style={styles.container}>
9+
<ConnectionStatusBar />
10+
<ChainSwitcher />
11+
<ScrollView contentContainerStyle={styles.content}>
12+
<SendTransaction />
13+
</ScrollView>
14+
</View>
15+
)
16+
}
17+
18+
const styles = StyleSheet.create({
19+
container: {
20+
flex: 1,
21+
backgroundColor: '#fff',
22+
paddingTop: 50,
23+
},
24+
content: {
25+
alignItems: 'center',
26+
paddingVertical: 20,
27+
flexGrow: 1,
28+
},
29+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ScrollView, StyleSheet, View } from 'react-native'
2+
import { GoogleAuth } from '@/components/GoogleAuth'
3+
import { OTPAuth } from '@/components/OTPAuth'
4+
import { PasskeyAuth } from '@/components/PasskeyAuth'
5+
6+
export default function LoginScreen() {
7+
return (
8+
<View style={styles.container}>
9+
<ScrollView contentContainerStyle={styles.content}>
10+
<GoogleAuth />
11+
<PasskeyAuth />
12+
<OTPAuth />
13+
</ScrollView>
14+
</View>
15+
)
16+
}
17+
18+
const styles = StyleSheet.create({
19+
container: {
20+
flex: 1,
21+
backgroundColor: '#fff',
22+
paddingTop: 50,
23+
},
24+
content: {
25+
alignItems: 'center',
26+
paddingVertical: 20,
27+
flexGrow: 1,
28+
},
29+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ActivityIndicator, StyleSheet, View } from 'react-native'
2+
3+
export default function OAuthCallback() {
4+
return (
5+
<View style={styles.container}>
6+
<ActivityIndicator size="large" color="#4285F4" />
7+
</View>
8+
)
9+
}
10+
11+
const styles = StyleSheet.create({
12+
container: {
13+
flex: 1,
14+
backgroundColor: '#fff',
15+
justifyContent: 'center',
16+
alignItems: 'center',
17+
},
18+
})

0 commit comments

Comments
 (0)