Site icon May's Notes

Expo 實作 Google 登入功能

a blurry photo of a colorful object

版本

  • expo 52
  • react-native 0.76.1
  • @react-native-google-signin/google-signin 13.1.0

設定 OAuth 同意畫面

1.到 Google Cloud Console 建立專案
2.進入憑證頁面
3.建立憑證 > 新增 OAuth 用戶端 ID,此時會提示你需要先設定同意畫面

4.User Type 選擇外部

5.接著填寫 APP 的資訊,後面步驟全部跳過即可。

創建 OAuth 用戶端 ID

只需要創建 Web 和 iOS

回到憑證,建立 OAuth 用戶端 ID

Web

iOS

繫結編號就是 app.json 中 ios.bundleIdentifier

安裝 react-native-google-signin

安裝 @react-native-google-signin/google-signin

npx expo install @react-native-google-signin/google-signin

在 app.json 的 plugins 中加上:

{
  "expo": {
    "plugins": [
      [
        "@react-native-google-signin/google-signin",
        {
          "iosUrlScheme": "com.googleusercontent.apps._some_id_here_"
        }
      ]
    ]
  }
}

iosUrlScheme 就是 iOS網址通訊協定,在剛剛建立的 iOS OAuth 用戶端ID裡面可以找到。

實作Google登入按鈕

修改 app.json 中的 scheme

{
  "expo": {
    "scheme": "your app name"
  }
}

新增一個 GoogleSigninButton.tsx

import { StyleSheet, Pressable, View } from 'react-native'
import { Image, Text } from 'tamagui'
import { useTranslation } from 'react-i18next'
// ...
import { useAuth } from '@/hooks'

export const GoogleSigninButton = () => {
  const { signIn } = useAuth()
  //...
  return (
    <Pressable style={[styles.container, { backgroundColor: color.primary }]} onPress={signIn}>
      <View style={styles.iconContainer}>
        <Image source={require('../../assets/images/google.png')} style={styles.icon} />
      </View>
      <Text style={styles.text} color={color.text} fontSize={18}>{t('SignInWithGoogle')}</Text>
    </Pressable>
  )
}

路由權限驗證

useAuth.ts

import { useContext } from 'react'
import { GoogleSignin } from '@react-native-google-signin/google-signin'
import { AuthContext } from '@/context'
import { useAuthStore } from '@/hooks'

GoogleSignin.configure({
  webClientId: process.env.EXPO_PUBLIC_WEB_CLIENT_ID,
})

export const useAuth = () => {
  const { setToken } = useAuthStore()

  const signIn = async () => {
    try {
      await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true })
      const userInfo = await GoogleSignin.signIn()
      console.log('userInfo', userInfo)

      // send idToken to server for verification
      const token = ....
      setToken(token)
    } catch (error) {
      console.error(error)
    }
  }

  const signOut = async () => {
    try {
      await GoogleSignin.signOut()
      setToken(null)
    } catch (error) {
      console.error(error)
    }
  }

  return { signIn, signOut }
}

app/_layout.tsx

<AuthProvider>
  <Stack>
    <Stack.Screen name="login" options={{ headerShown: false }} />
    <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
  </Stack>
</AuthProvider>

AuthProvider.tsx

import { PropsWithChildren } from 'react'
import { AuthContext } from '@/context'
import { useAuthStore } from '@/hooks'

export const AuthProvider = ({ children }: PropsWithChildren) => {
  const { token, setToken } = useAuthStore()
  return (
    <AuthContext.Provider value={{ token, setToken }}>
      {children}
    </AuthContext.Provider>
  )
}

需要登入才能訪問的路由記得加上權限驗證,比如:

// (tabs)/_layout.tsx
import React from 'react'
import { Tabs, Redirect } from 'expo-router'
import { useTranslation } from 'react-i18next'

import { TabBar } from '@/components'
import { useAuthStore } from '@/hooks'

export default function TabLayout() {
  const { t } = useTranslation()
  const { token } = useAuthStore()

  if (!token) {
    return <Redirect href="/login" />
  }

  return (
    <Tabs
      backBehavior="history"
      screenOptions={{ headerShown: false }}
      tabBar={(props) => <TabBar {...props} />}
    >
      <Tabs.Screen name="index" options={{ title: t('Home') }} />
    </Tabs>
  )
}

Exit mobile version